@taskless-app/swarm 0.6.17 → 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 +225 -14
  2. package/dist/overmind.js +452 -52
  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() : "";
@@ -1455,6 +1541,14 @@ var init_queuePlan = __esm({
1455
1541
  }
1456
1542
  });
1457
1543
 
1544
+ // ../swarm-core/src/zoneMemory.ts
1545
+ var init_zoneMemory = __esm({
1546
+ "../swarm-core/src/zoneMemory.ts"() {
1547
+ "use strict";
1548
+ init_time();
1549
+ }
1550
+ });
1551
+
1458
1552
  // ../swarm-core/src/index.ts
1459
1553
  var init_src = __esm({
1460
1554
  "../swarm-core/src/index.ts"() {
@@ -1463,6 +1557,7 @@ var init_src = __esm({
1463
1557
  init_timeline();
1464
1558
  init_reviewPhase();
1465
1559
  init_mergeGate();
1560
+ init_mergeQueue();
1466
1561
  init_verifyVerdict();
1467
1562
  init_modelPolicy();
1468
1563
  init_status();
@@ -1485,6 +1580,7 @@ var init_src = __esm({
1485
1580
  init_codeZones();
1486
1581
  init_effort();
1487
1582
  init_queuePlan();
1583
+ init_zoneMemory();
1488
1584
  }
1489
1585
  });
1490
1586
 
@@ -2071,7 +2167,7 @@ function tokenFromFile() {
2071
2167
  return "";
2072
2168
  }
2073
2169
  }
2074
- 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;
2075
2171
  var init_config = __esm({
2076
2172
  "src/config.ts"() {
2077
2173
  "use strict";
@@ -2144,6 +2240,7 @@ var init_config = __esm({
2144
2240
  SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
2145
2241
  SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
2146
2242
  SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
2243
+ SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
2147
2244
  REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join4(SWARM_HOME, "review-cutoff.json");
2148
2245
  SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
2149
2246
  SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
@@ -2275,7 +2372,7 @@ var init_package = __esm({
2275
2372
  "package.json"() {
2276
2373
  package_default = {
2277
2374
  name: "@taskless-app/swarm",
2278
- version: "0.6.17",
2375
+ version: "0.6.19",
2279
2376
  type: "module",
2280
2377
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
2281
2378
  keywords: [
@@ -2325,7 +2422,7 @@ var init_package = __esm({
2325
2422
  execa: "^10.0.0",
2326
2423
  zod: "3.25.76"
2327
2424
  },
2328
- gitHead: "a4ed164a36295bd7ebc27e7f6fe73809e09b142a"
2425
+ gitHead: "a61c26b5e254ef5383d918f14a9f6e8e8a09904a"
2329
2426
  };
2330
2427
  }
2331
2428
  });
@@ -2492,8 +2589,8 @@ function describeError(e, maxDepth = 4) {
2492
2589
  const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
2493
2590
  const code = cur.code;
2494
2591
  const codeText = code === void 0 || code === null ? "" : String(code);
2495
- const label = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2496
- 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);
2497
2594
  cur = cur.cause;
2498
2595
  }
2499
2596
  return parts.join(" \u2190 ") || String(e);
@@ -5163,18 +5260,18 @@ function isTransientResourceError(e) {
5163
5260
  const code = e?.code;
5164
5261
  return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
5165
5262
  }
5166
- function logTransientSpawnError(label, e) {
5263
+ function logTransientSpawnError(label2, e) {
5167
5264
  if (isTransientResourceError(e)) {
5168
5265
  const code = e.code;
5169
- 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`);
5170
5267
  } else {
5171
- 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)}`);
5172
5269
  }
5173
5270
  }
5174
5271
  function parseVmStatAvailableBytes(vmStatOut) {
5175
5272
  const page = Number(vmStatOut.match(/page size of (\d+) bytes/)?.[1]) || 4096;
5176
- const pages = (label) => {
5177
- 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+)`));
5178
5275
  return m ? Number(m[1]) : 0;
5179
5276
  };
5180
5277
  return (pages("free") + pages("inactive") + pages("speculative") + pages("purgeable")) * page;
@@ -6742,6 +6839,27 @@ var SwarmClient = class _SwarmClient {
6742
6839
  const sc = res.structuredContent ?? {};
6743
6840
  return parsePendingRepoRules(sc.rules);
6744
6841
  }
6842
+ /**
6843
+ * ПАМЯТЬ ЗОНЫ (TASWM-466) из того же swarm_repo_config — отдельное поле `zoneMemory`,
6844
+ * карта «зона → заметки», только принятое вердиктом. Демон кладёт заметки НУЖНОЙ зоны в
6845
+ * промпт дрона (см. zoneNotes.ts), поэтому отдаём карту КАК ЕСТЬ, сырым объектом: разбор
6846
+ * и выбор зоны — одно место, и оно рядом с рендером блока.
6847
+ *
6848
+ * Пустая карта — памяти нет / поля в ответе прода нет (прод старее демона) / repo не
6849
+ * смэплен / прод недоступен. Все четыре случая РАБОЧИЕ: промпт собирается как раньше.
6850
+ */
6851
+ async repoZoneMemory(repoFullName) {
6852
+ let res;
6853
+ try {
6854
+ res = await this.call("swarm_repo_config", { repoFullName });
6855
+ } catch {
6856
+ return {};
6857
+ }
6858
+ if (res?.isError) return {};
6859
+ const sc = res.structuredContent ?? {};
6860
+ const m = sc.zoneMemory;
6861
+ return m && typeof m === "object" && !Array.isArray(m) ? m : {};
6862
+ }
6745
6863
  /**
6746
6864
  * Live КОМАНДЫ ПОДГОТОВКИ worktree из Taskless (TASWM-312): тот же swarm_repo_config по
6747
6865
  * repoFullName — Project.swarmConfig.prepare. Настройка сильнее детекта демона (deps.ts):
@@ -8439,6 +8557,11 @@ init_config();
8439
8557
 
8440
8558
  // src/mergeGate.ts
8441
8559
  init_src();
8560
+
8561
+ // src/mergeQueue.ts
8562
+ init_src();
8563
+
8564
+ // src/prNumber.ts
8442
8565
  function prNumberOf(ref) {
8443
8566
  const s = (ref || "").trim();
8444
8567
  if (!s) return null;
@@ -8447,6 +8570,76 @@ function prNumberOf(ref) {
8447
8570
  const bare = s.match(/^#?(\d+)$/);
8448
8571
  return bare ? Number(bare[1]) : null;
8449
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
8450
8643
  function findRunByPr(runs, ref) {
8451
8644
  const num = prNumberOf(ref);
8452
8645
  if (num == null) return null;
@@ -8458,17 +8651,34 @@ function findRunByPr(runs, ref) {
8458
8651
  return best;
8459
8652
  }
8460
8653
  function makeMergeGateResolver(deps) {
8461
- return async (ref) => {
8462
- const run = findRunByPr(deps.listRuns(), ref);
8463
- 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) => {
8464
8658
  const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
8465
- const decision = computeMergeGate({
8659
+ return computeMergeGate({
8466
8660
  hasRun: true,
8467
8661
  autoReview,
8468
8662
  reviewVerdict: run.review_verdict,
8469
8663
  autoVerify: deps.autoVerify === true,
8470
8664
  verifyVerdict: run.verify_verdict ?? null
8471
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);
8472
8682
  return {
8473
8683
  ...decision,
8474
8684
  run: {
@@ -8481,6 +8691,7 @@ function makeMergeGateResolver(deps) {
8481
8691
  }
8482
8692
  };
8483
8693
  };
8694
+ return { gate, queue: queueFor };
8484
8695
  }
8485
8696
 
8486
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() : "";
@@ -1527,6 +1613,16 @@ var init_queuePlan = __esm({
1527
1613
  }
1528
1614
  });
1529
1615
 
1616
+ // ../swarm-core/src/zoneMemory.ts
1617
+ var ZONE_NOTES_PER_RUN_MAX;
1618
+ var init_zoneMemory = __esm({
1619
+ "../swarm-core/src/zoneMemory.ts"() {
1620
+ "use strict";
1621
+ init_time();
1622
+ ZONE_NOTES_PER_RUN_MAX = 2;
1623
+ }
1624
+ });
1625
+
1530
1626
  // ../swarm-core/src/index.ts
1531
1627
  var init_src = __esm({
1532
1628
  "../swarm-core/src/index.ts"() {
@@ -1535,6 +1631,7 @@ var init_src = __esm({
1535
1631
  init_timeline();
1536
1632
  init_reviewPhase();
1537
1633
  init_mergeGate();
1634
+ init_mergeQueue();
1538
1635
  init_verifyVerdict();
1539
1636
  init_modelPolicy();
1540
1637
  init_status();
@@ -1557,6 +1654,7 @@ var init_src = __esm({
1557
1654
  init_codeZones();
1558
1655
  init_effort();
1559
1656
  init_queuePlan();
1657
+ init_zoneMemory();
1560
1658
  }
1561
1659
  });
1562
1660
 
@@ -2150,6 +2248,7 @@ __export(config_exports, {
2150
2248
  SWARM_MAX_FD_LOAD_FACTOR: () => SWARM_MAX_FD_LOAD_FACTOR,
2151
2249
  SWARM_MAX_IN_FLIGHT: () => SWARM_MAX_IN_FLIGHT,
2152
2250
  SWARM_MAX_LOAD_FACTOR: () => SWARM_MAX_LOAD_FACTOR,
2251
+ SWARM_MERGE_QUEUE: () => SWARM_MERGE_QUEUE,
2153
2252
  SWARM_MIN_FREE_GB: () => SWARM_MIN_FREE_GB,
2154
2253
  SWARM_OUTBOX: () => SWARM_OUTBOX,
2155
2254
  SWARM_PROD_HEARTBEAT_MS: () => SWARM_PROD_HEARTBEAT_MS,
@@ -2272,7 +2371,7 @@ function reviewCutoffAt() {
2272
2371
  }
2273
2372
  return at;
2274
2373
  }
2275
- 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;
2276
2375
  var init_config = __esm({
2277
2376
  "src/config.ts"() {
2278
2377
  "use strict";
@@ -2345,6 +2444,7 @@ var init_config = __esm({
2345
2444
  SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
2346
2445
  SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
2347
2446
  SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
2447
+ SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
2348
2448
  REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join5(SWARM_HOME, "review-cutoff.json");
2349
2449
  SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
2350
2450
  SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
@@ -2390,12 +2490,12 @@ function isTransientResourceError(e) {
2390
2490
  const code = e?.code;
2391
2491
  return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
2392
2492
  }
2393
- function logTransientSpawnError(label, e) {
2493
+ function logTransientSpawnError(label2, e) {
2394
2494
  if (isTransientResourceError(e)) {
2395
2495
  const code = e.code;
2396
- 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`);
2397
2497
  } else {
2398
- 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)}`);
2399
2499
  }
2400
2500
  }
2401
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) {
@@ -2406,8 +2506,8 @@ function resourcesOk(load1, cpus, freeGb, maxLoadFactor = SWARM_MAX_LOAD_FACTOR,
2406
2506
  }
2407
2507
  function parseVmStatAvailableBytes(vmStatOut) {
2408
2508
  const page = Number(vmStatOut.match(/page size of (\d+) bytes/)?.[1]) || 4096;
2409
- const pages = (label) => {
2410
- 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+)`));
2411
2511
  return m ? Number(m[1]) : 0;
2412
2512
  };
2413
2513
  return (pages("free") + pages("inactive") + pages("speculative") + pages("purgeable")) * page;
@@ -2859,8 +2959,8 @@ function describeError(e, maxDepth = 4) {
2859
2959
  const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
2860
2960
  const code = cur.code;
2861
2961
  const codeText = code === void 0 || code === null ? "" : String(code);
2862
- const label = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2863
- 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);
2864
2964
  cur = cur.cause;
2865
2965
  }
2866
2966
  return parts.join(" \u2190 ") || String(e);
@@ -3981,37 +4081,37 @@ async function materializeAttachments(opts) {
3981
4081
  let index = 0;
3982
4082
  for (const ref of opts.refs) {
3983
4083
  index++;
3984
- const label = ref.caption || ref.url;
4084
+ const label2 = ref.caption || ref.url;
3985
4085
  const cls = classifyAttachment(ref);
3986
4086
  if ("skip" in cls) {
3987
- result.skipped.push(`${label} \u2014 ${cls.skip}`);
4087
+ result.skipped.push(`${label2} \u2014 ${cls.skip}`);
3988
4088
  continue;
3989
4089
  }
3990
4090
  if (result.files.length >= maxFiles) {
3991
- 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`);
3992
4092
  continue;
3993
4093
  }
3994
4094
  if (ref.size != null && ref.size > maxFile) {
3995
- 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`);
3996
4096
  continue;
3997
4097
  }
3998
4098
  let file = null;
3999
4099
  try {
4000
4100
  file = await opts.fetchFile(ref.url);
4001
4101
  } catch (e) {
4002
- 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)})`);
4003
4103
  continue;
4004
4104
  }
4005
4105
  if (!file) {
4006
- 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)`);
4007
4107
  continue;
4008
4108
  }
4009
4109
  if (file.bytes.byteLength > maxFile) {
4010
- 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`);
4011
4111
  continue;
4012
4112
  }
4013
4113
  if (total + file.bytes.byteLength > maxTotal) {
4014
- 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)}`);
4015
4115
  continue;
4016
4116
  }
4017
4117
  const name = attachmentFileName(index, ref.caption, attachmentExt(ref, file.contentType));
@@ -4022,7 +4122,7 @@ async function materializeAttachments(opts) {
4022
4122
  }
4023
4123
  await io.writeFile(join20(dir, name), file.bytes);
4024
4124
  } catch (e) {
4025
- 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)})`);
4026
4126
  continue;
4027
4127
  }
4028
4128
  total += file.bytes.byteLength;
@@ -4606,6 +4706,29 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4606
4706
  const text = res.content?.find((c) => c.type === "text")?.text;
4607
4707
  return typeof text === "string" && text.trim() ? text : null;
4608
4708
  }
4709
+ async function proposeZoneNotes(input) {
4710
+ const res = await safeCall("swarm_zone_note_propose", {
4711
+ repoFullName: input.repoFullName,
4712
+ notes: input.notes,
4713
+ taskSlug: input.taskSlug,
4714
+ ...input.by ? { by: input.by } : {},
4715
+ ...input.prUrl ? { prUrl: input.prUrl } : {}
4716
+ });
4717
+ if (!res || res.isError) return null;
4718
+ const text = res.content?.find((c) => c.type === "text")?.text;
4719
+ return typeof text === "string" && text.trim() ? text : null;
4720
+ }
4721
+ async function confirmZoneNotes(input) {
4722
+ const res = await safeCall("swarm_zone_note_confirm", {
4723
+ repoFullName: input.repoFullName,
4724
+ notes: input.notes,
4725
+ ...input.zone ? { zone: input.zone } : {},
4726
+ ...input.by ? { by: input.by } : {}
4727
+ });
4728
+ if (!res || res.isError) return null;
4729
+ const text = res.content?.find((c) => c.type === "text")?.text;
4730
+ return typeof text === "string" && text.trim() ? text : null;
4731
+ }
4609
4732
  async function resolveId(idOrSlug) {
4610
4733
  const cached = idCache.get(idOrSlug);
4611
4734
  if (cached) return cached;
@@ -4647,7 +4770,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4647
4770
  return await safeCall("update_task", { id, assigneeId: userId }) != null;
4648
4771
  },
4649
4772
  uploadShot: (file) => uploadShot(safeCall, file, fetchImpl),
4650
- addComment: async (idOrSlug, body, label, attachments) => {
4773
+ addComment: async (idOrSlug, body, label2, attachments) => {
4651
4774
  const id = await resolveId(idOrSlug);
4652
4775
  if (!id) {
4653
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)`);
@@ -4666,7 +4789,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4666
4789
  body: parts[i],
4667
4790
  // Метки роя — на КАЖДОЙ части: без них хвост отчёта выглядит в ленте задачи
4668
4791
  // чужой репликой без автора (swarmKind/swarmDroneName рисуются чипами).
4669
- ...label ?? {},
4792
+ ...label2 ?? {},
4670
4793
  // TASWM-294: скрины — к ПЕРВОЙ части, к шапке отчёта. Разносить их по
4671
4794
  // частям треда нельзя: человек открывает задачу и должен увидеть, как
4672
4795
  // это выглядит, в том же комменте, что и «что сделано».
@@ -4688,6 +4811,8 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4688
4811
  reportAcceptanceCheck,
4689
4812
  proposeRepoRules,
4690
4813
  decideRepoRules,
4814
+ proposeZoneNotes,
4815
+ confirmZoneNotes,
4691
4816
  createTask: async (input) => {
4692
4817
  if (!input.projectId) {
4693
4818
  throw new Error("createTask: projectId \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u2014 \u0430\u0432\u0442\u043E-\u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0440\u043E\u0435\u043C \u0431\u0435\u0437 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u043E (TASWM-204)");
@@ -7641,6 +7766,27 @@ var SwarmClient = class _SwarmClient {
7641
7766
  const sc = res.structuredContent ?? {};
7642
7767
  return parsePendingRepoRules(sc.rules);
7643
7768
  }
7769
+ /**
7770
+ * ПАМЯТЬ ЗОНЫ (TASWM-466) из того же swarm_repo_config — отдельное поле `zoneMemory`,
7771
+ * карта «зона → заметки», только принятое вердиктом. Демон кладёт заметки НУЖНОЙ зоны в
7772
+ * промпт дрона (см. zoneNotes.ts), поэтому отдаём карту КАК ЕСТЬ, сырым объектом: разбор
7773
+ * и выбор зоны — одно место, и оно рядом с рендером блока.
7774
+ *
7775
+ * Пустая карта — памяти нет / поля в ответе прода нет (прод старее демона) / repo не
7776
+ * смэплен / прод недоступен. Все четыре случая РАБОЧИЕ: промпт собирается как раньше.
7777
+ */
7778
+ async repoZoneMemory(repoFullName) {
7779
+ let res;
7780
+ try {
7781
+ res = await this.call("swarm_repo_config", { repoFullName });
7782
+ } catch {
7783
+ return {};
7784
+ }
7785
+ if (res?.isError) return {};
7786
+ const sc = res.structuredContent ?? {};
7787
+ const m = sc.zoneMemory;
7788
+ return m && typeof m === "object" && !Array.isArray(m) ? m : {};
7789
+ }
7644
7790
  /**
7645
7791
  * Live КОМАНДЫ ПОДГОТОВКИ worktree из Taskless (TASWM-312): тот же swarm_repo_config по
7646
7792
  * repoFullName — Project.swarmConfig.prepare. Настройка сильнее детекта демона (deps.ts):
@@ -10415,6 +10561,22 @@ async function prHeadBranch(prUrl, run = realRunner) {
10415
10561
  return null;
10416
10562
  }
10417
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
+ }
10418
10580
  async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
10419
10581
  if (!branch || !repoDir) return [];
10420
10582
  try {
@@ -10625,6 +10787,54 @@ init_shotUpload();
10625
10787
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10626
10788
  import { z } from "zod";
10627
10789
  init_git();
10790
+
10791
+ // src/zoneNotes.ts
10792
+ init_src();
10793
+ function parseZoneMemory(raw, zone) {
10794
+ const key = normalizeZoneLabel(zone);
10795
+ if (!key || !raw || typeof raw !== "object" || Array.isArray(raw)) return [];
10796
+ const notes = raw[key];
10797
+ if (!Array.isArray(notes)) return [];
10798
+ const out = [];
10799
+ for (const item of notes) {
10800
+ if (!item || typeof item !== "object") continue;
10801
+ const n = item;
10802
+ const id = typeof n.id === "string" ? n.id : "";
10803
+ const claim = typeof n.claim === "string" ? n.claim.trim() : "";
10804
+ if (id && claim) out.push({ id, claim });
10805
+ }
10806
+ return out;
10807
+ }
10808
+ function normalizeZoneLabel(zone) {
10809
+ return (zone ?? "").trim().replace(/^area:/i, "").trim().toLowerCase();
10810
+ }
10811
+ function renderZoneMemoryBlock(zone, notes) {
10812
+ const label2 = normalizeZoneLabel(zone);
10813
+ if (!label2) return "";
10814
+ const head = notes.length ? [
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):`,
10816
+ ...notes.map((n) => `\u2022 [${n.id}] ${n.claim}`),
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.",
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.`
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.`];
10820
+ return [
10821
+ ...head,
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.`
10823
+ ].join("\n");
10824
+ }
10825
+ function collectZoneNoteConfirmations(report) {
10826
+ const seen = /* @__PURE__ */ new Set();
10827
+ const out = [];
10828
+ for (const raw of report.used ?? []) {
10829
+ const s = typeof raw === "string" ? raw.trim() : "";
10830
+ if (!s || seen.has(s)) continue;
10831
+ seen.add(s);
10832
+ out.push(s);
10833
+ }
10834
+ return out;
10835
+ }
10836
+
10837
+ // src/droneServer.ts
10628
10838
  init_src();
10629
10839
 
10630
10840
  // src/reviewCycle.ts
@@ -11014,11 +11224,26 @@ ${notFoundText}` : notFoundText }],
11014
11224
  ).optional().describe(
11015
11225
  `\u041F\u0420\u0410\u0412\u0418\u041B\u041E \u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u0418, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u0432 \u043D\u0451\u043C \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E (TASWM-431) \u2014 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${DRONE_RULES_PER_RUN_MAX} \u0437\u0430 \u043F\u0440\u043E\u0433\u043E\u043D \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u043E\u0431\u043E \u0447\u0442\u043E \u0442\u044B \u0420\u0415\u0410\u041B\u042C\u041D\u041E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u043F\u043E\u0442\u0440\u0430\u0442\u0438\u043B \u0432\u0440\u0435\u043C\u044F (\xAB\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B \u0436\u0434\u0443\u0442 \u043F\u043E\u0441\u0442\u0433\u0440\u0435\u0441 \u043D\u0430 5433\xBB). \u041E\u0431\u0449\u0435\u0435 \u043C\u0435\u0441\u0442\u043E (\xAB\u0442\u0435\u0441\u0442\u044B \u0433\u043E\u043D\u044F\u044E\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 pnpm test\xBB) \u043F\u0440\u0430\u0432\u0438\u043B\u043E\u043C \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F. \u0423\u0436\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u0435 \u043E\u0442\u0441\u0435\u0435\u0442\u0441\u044F \u0441\u0430\u043C\u043E. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0432 \u0441\u0432\u043E\u0451\u043C \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435.`
11016
11226
  ),
11227
+ // TASWM-466: память ЗОНЫ — то же, что правило репозитория, но адресат кусок кода.
11228
+ // Форму судит прод (там бюджет и дедуп), здесь схема свободная по той же причине,
11229
+ // что у rules: гейт в двух местах разъехался бы.
11230
+ zone_notes: z.array(
11231
+ z.object({
11232
+ zone: z.string().optional().describe("\u0437\u043E\u043D\u0430 \u043A\u043E\u0434\u0430 (area:*). \u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u0430 \u2014 \u0432\u043E\u0437\u044C\u043C\u0451\u0442\u0441\u044F \u0437\u043E\u043D\u0430 \u0442\u0432\u043E\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430"),
11233
+ claim: z.string().describe("\u0441\u0430\u043C\u043E \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435, \u043E\u0434\u043D\u043E\u0439 \u0444\u0440\u0430\u0437\u043E\u0439"),
11234
+ why: z.string().describe("\u043F\u043E\u0447\u0435\u043C\u0443 \u044D\u0442\u043E \u0442\u0430\u043A \u2014 \u0431\u0435\u0437 \u043F\u0440\u0438\u0447\u0438\u043D\u044B \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u043D\u0435 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F")
11235
+ })
11236
+ ).optional().describe(
11237
+ `\u0417\u0410\u041C\u0415\u0422\u041A\u0410 \u0412 \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B (TASWM-466) \u2014 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${ZONE_NOTES_PER_RUN_MAX} \u0437\u0430 \u043F\u0440\u043E\u0433\u043E\u043D: \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439, \u0433\u0440\u0430\u0431\u043B\u0438 \u0441 \u0446\u0435\u043D\u043E\u0439, \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. \u0423\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 \u0442\u0430\u043C-\u0442\u043E\xBB) \u0437\u0430\u043C\u0435\u0442\u043A\u043E\u0439 \u041D\u0415 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F: \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. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u0443\u0436\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u0435 \u043E\u0442\u0441\u0435\u0435\u0442\u0441\u044F \u0441\u0430\u043C\u043E.`
11238
+ ),
11239
+ zone_notes_used: z.array(z.string()).optional().describe(
11240
+ "\u0417\u0410\u041C\u0415\u0422\u041A\u0418 \u0417\u041E\u041D\u042B, \u041A\u041E\u0422\u041E\u0420\u042B\u0415 \u041F\u0420\u0418\u0413\u041E\u0414\u0418\u041B\u0418\u0421\u042C (TASWM-466): id \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u043F\u0430\u043C\u044F\u0442\u044C \u0437\u043E\u043D\u044B\xBB \u0432 \u0442\u0432\u043E\u0451\u043C \u043F\u0440\u043E\u043C\u043F\u0442\u0435 (\u0438\u043B\u0438 \u0441\u0430\u043C \u0442\u0435\u043A\u0441\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F). \u0418\u043C \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0441\u0447\u0451\u0442\u0447\u0438\u043A \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439 \u2014 \u044D\u0442\u043E \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0435, \u0447\u0435\u043C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044F \u0432 \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u043D\u043E\u0439 \u043F\u0430\u043C\u044F\u0442\u0438 \u0437\u043E\u043D\u044B. \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."
11241
+ ),
11017
11242
  rule_verdicts: z.array(z.object({ field: z.enum([...RULE_FIELDS]), apply: z.boolean() })).optional().describe(
11018
11243
  "\u0422\u041E\u041B\u042C\u041A\u041E \u0414\u041B\u042F \u0420\u0415\u0412\u042C\u042E\u0415\u0420\u0410 (TASWM-431): \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043F\u0440\u0438\u043D\u0451\u0441 \u0434\u0440\u043E\u043D (\u043E\u043D\u0438 \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u044B \u0432 \u0442\u0432\u043E\u0451\u043C \u043F\u0440\u043E\u043C\u043F\u0442\u0435). apply=true \u2014 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u0435, apply=false \u2014 \u043E\u0442\u043F\u0438\u0441\u043A\u0430, \u0441\u043D\u044F\u0442\u044C. verify/conventions/brief \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0442\u0441\u044F \u0432 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0443 \u0441\u0440\u0430\u0437\u0443, env/zones/spine \u0443\u0439\u0434\u0443\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0432 \u043B\u044E\u0431\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435."
11019
11244
  )
11020
11245
  },
11021
- guard(async ({ pr_url, verdict, summary, screenshots, rules, rule_verdicts }) => {
11246
+ guard(async ({ pr_url, verdict, summary, screenshots, rules, rule_verdicts, zone_notes, zone_notes_used }) => {
11022
11247
  const run = await store.myRun();
11023
11248
  let repoFullNameCache = null;
11024
11249
  const repoFullNameOnce = () => repoFullNameCache ??= remoteRepoFullName(opts.worktree ?? process.cwd()).catch(() => null);
@@ -11051,6 +11276,46 @@ ${notFoundText}` : notFoundText }],
11051
11276
  return `
11052
11277
 
11053
11278
  \u{1F4CC} ${line}`;
11279
+ };
11280
+ const zoneMemoryNote = async (taskSlug) => {
11281
+ const used = collectZoneNoteConfirmations({ used: zone_notes_used });
11282
+ if (!zone_notes?.length && used.length === 0) return "";
11283
+ const zone = normalizeZoneLabel(run?.area);
11284
+ const repoFullName = await repoFullNameOnce();
11285
+ if (!repoFullName) {
11286
+ await store.addEvent(null, "step", "\u0437\u0430\u043C\u0435\u0442\u043A\u0438 \u043E \u0437\u043E\u043D\u0435 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u044B: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo \u043F\u043E origin");
11287
+ return "\n\n\u26A0\uFE0F \u0417\u0430\u043C\u0435\u0442\u043A\u0438 \u043E \u0437\u043E\u043D\u0435 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u044B: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo.";
11288
+ }
11289
+ const lines = [];
11290
+ if (zone_notes?.length) {
11291
+ if (!opts.taskless?.proposeZoneNotes) lines.push("\u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u043E \u0437\u043E\u043D\u0435 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430: Taskless \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D");
11292
+ else if (!taskSlug) lines.push("\u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u043E \u0437\u043E\u043D\u0435 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430: \u0441\u043B\u0430\u0433 \u0437\u0430\u0434\u0430\u0447\u0438 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D");
11293
+ else {
11294
+ const addressed = zone_notes.map((n) => ({ zone: normalizeZoneLabel(n.zone) || zone, claim: n.claim, why: n.why })).filter((n) => n.zone);
11295
+ if (addressed.length < zone_notes.length) lines.push("\u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0431\u0435\u0437 \u0437\u043E\u043D\u044B \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430: \u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435\u0442 area, \u0430 \u0432 \u0441\u0430\u043C\u043E\u0439 \u0437\u0430\u043C\u0435\u0442\u043A\u0435 \u0437\u043E\u043D\u0430 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0430");
11296
+ if (addressed.length > 0) {
11297
+ const text = await opts.taskless.proposeZoneNotes({
11298
+ repoFullName,
11299
+ notes: addressed,
11300
+ taskSlug,
11301
+ ...run?.name ? { by: run.name } : {},
11302
+ prUrl: pr_url
11303
+ }).catch((e) => `\u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430: ${e instanceof Error ? e.message : String(e)}`);
11304
+ lines.push(text ?? "\u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u043E \u0437\u043E\u043D\u0435 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430 (\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u0438\u043B\u0438 \u0442\u0443\u043B \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D)");
11305
+ }
11306
+ }
11307
+ }
11308
+ if (used.length > 0) {
11309
+ if (!opts.taskless?.confirmZoneNotes) lines.push("\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0437\u0430\u043C\u0435\u0442\u043E\u043A \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: Taskless \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D");
11310
+ else {
11311
+ const text = await opts.taskless.confirmZoneNotes({ repoFullName, notes: used, ...zone ? { zone } : {}, ...run?.name ? { by: run.name } : {} }).catch((e) => `\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: ${e instanceof Error ? e.message : String(e)}`);
11312
+ lines.push(text ?? "\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0437\u0430\u043C\u0435\u0442\u043E\u043A \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E (\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u0438\u043B\u0438 \u0442\u0443\u043B \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D)");
11313
+ }
11314
+ }
11315
+ for (const line of lines) await store.addEvent(null, "step", `\u043F\u0430\u043C\u044F\u0442\u044C \u0437\u043E\u043D\u044B: ${line}`);
11316
+ return lines.length ? `
11317
+
11318
+ \u{1F9E0} ${lines.join("; ")}` : "";
11054
11319
  };
11055
11320
  const reportSlug = (slug) => opts.expectedTaskSlug ?? slug;
11056
11321
  const uploadShots = async () => {
@@ -11252,15 +11517,16 @@ ${report}${shots.warning}`,
11252
11517
  await store.addEvent(null, "pr_opened", pr_url);
11253
11518
  await postReport(reportSlug(run.task_slug), t.cycle ?? run.review_cycle ?? 0);
11254
11519
  const rulesNote = await offerRules(reportSlug(run.task_slug));
11520
+ const zoneNote = await zoneMemoryNote(reportSlug(run.task_slug));
11255
11521
  if (t.isFixIteration) {
11256
11522
  await store.addEvent(
11257
11523
  null,
11258
11524
  "step",
11259
11525
  `\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F: \u043D\u043E\u0432\u044B\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u043F\u043E\u0441\u043B\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \xAB\u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438\xBB \u2014 \u043E\u0442\u043A\u0440\u044B\u0442 \u0446\u0438\u043A\u043B \u0440\u0435-\u0440\u0435\u0432\u044C\u044E #${t.cycle}`
11260
11526
  );
11261
- return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F)${shotsNote(shots)}${rulesNote}` }] };
11527
+ return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F)${shotsNote(shots)}${rulesNote}${zoneNote}` }] };
11262
11528
  }
11263
- return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}${rulesNote}` }] };
11529
+ return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}${rulesNote}${zoneNote}` }] };
11264
11530
  }
11265
11531
  await store.updateRun(null, { status: "review", pr_url, ...branchFix?.patch ?? {} });
11266
11532
  await store.addEvent(null, "pr_opened", pr_url);
@@ -11268,7 +11534,8 @@ ${report}${shots.warning}`,
11268
11534
  if (fallbackSlug) await postReport(fallbackSlug, void 0);
11269
11535
  else await store.addEvent(null, "step", `\u043E\u0442\u0447\u0451\u0442 \u0434\u0440\u043E\u043D\u0430 (\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430 \u2014 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435\u0442 \u0432 \u0441\u0442\u043E\u0440\u0435): ${report}`);
11270
11536
  const fallbackRulesNote = await offerRules(fallbackSlug);
11271
- return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}${fallbackRulesNote}` }] };
11537
+ const fallbackZoneNote = await zoneMemoryNote(fallbackSlug);
11538
+ return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}${fallbackRulesNote}${fallbackZoneNote}` }] };
11272
11539
  }
11273
11540
  await store.updateRun(null, { status: "done" });
11274
11541
  await store.addEvent(null, "done", pr_url ? `\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u0431\u0435\u0437 GitHub-PR (${pr_url})` : "\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u0431\u0435\u0437 GitHub-PR");
@@ -12346,6 +12613,11 @@ function makeDroneCapReader(opts) {
12346
12613
 
12347
12614
  // src/mergeGate.ts
12348
12615
  init_src();
12616
+
12617
+ // src/mergeQueue.ts
12618
+ init_src();
12619
+
12620
+ // src/prNumber.ts
12349
12621
  function prNumberOf(ref) {
12350
12622
  const s = (ref || "").trim();
12351
12623
  if (!s) return null;
@@ -12354,6 +12626,79 @@ function prNumberOf(ref) {
12354
12626
  const bare = s.match(/^#?(\d+)$/);
12355
12627
  return bare ? Number(bare[1]) : null;
12356
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
12357
12702
  function findRunByPr(runs, ref) {
12358
12703
  const num = prNumberOf(ref);
12359
12704
  if (num == null) return null;
@@ -12365,17 +12710,34 @@ function findRunByPr(runs, ref) {
12365
12710
  return best;
12366
12711
  }
12367
12712
  function makeMergeGateResolver(deps) {
12368
- return async (ref) => {
12369
- const run = findRunByPr(deps.listRuns(), ref);
12370
- 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) => {
12371
12717
  const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
12372
- const decision = computeMergeGate({
12718
+ return computeMergeGate({
12373
12719
  hasRun: true,
12374
12720
  autoReview,
12375
12721
  reviewVerdict: run.review_verdict,
12376
12722
  autoVerify: deps.autoVerify === true,
12377
12723
  verifyVerdict: run.verify_verdict ?? null
12378
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);
12379
12741
  return {
12380
12742
  ...decision,
12381
12743
  run: {
@@ -12388,6 +12750,7 @@ function makeMergeGateResolver(deps) {
12388
12750
  }
12389
12751
  };
12390
12752
  };
12753
+ return { gate, queue: queueFor };
12391
12754
  }
12392
12755
 
12393
12756
  // src/envGate.ts
@@ -12828,7 +13191,7 @@ async function changedPaths(worktree, opts = {}) {
12828
13191
  // package.json
12829
13192
  var package_default = {
12830
13193
  name: "@taskless-app/swarm",
12831
- version: "0.6.17",
13194
+ version: "0.6.19",
12832
13195
  type: "module",
12833
13196
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
12834
13197
  keywords: [
@@ -12878,7 +13241,7 @@ var package_default = {
12878
13241
  execa: "^10.0.0",
12879
13242
  zod: "3.25.76"
12880
13243
  },
12881
- gitHead: "a4ed164a36295bd7ebc27e7f6fe73809e09b142a"
13244
+ gitHead: "a61c26b5e254ef5383d918f14a9f6e8e8a09904a"
12882
13245
  };
12883
13246
 
12884
13247
  // src/version.ts
@@ -14581,8 +14944,8 @@ async function executeHostCommands(opts) {
14581
14944
  }
14582
14945
  async function executeOne(cmd, opts) {
14583
14946
  const { effects, ack, statePath } = opts;
14584
- const label = hostCommandLabel(cmd);
14585
- 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}`);
14586
14949
  try {
14587
14950
  switch (cmd.kind) {
14588
14951
  case "pause":
@@ -15465,6 +15828,14 @@ function lastConflictAttemptMs(db, runId) {
15465
15828
  if (!evs.length) return null;
15466
15829
  return Date.parse(evs[evs.length - 1].at.replace(" ", "T") + "Z");
15467
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
+ }
15468
15839
  function lastCiCheckMs(db, runId) {
15469
15840
  const evs = db.events(runId).filter((e) => e.kind === "ci_wake" || e.kind === "ci_checked");
15470
15841
  if (!evs.length) return null;
@@ -15812,7 +16183,7 @@ ${priorReview}` : `\u0417\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F \u2014
15812
16183
  }
15813
16184
  var HERO_CONTINUE_NUDGE = "\u0422\u0435\u0431\u044F \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B\u0438, \u0413\u0435\u0440\u043E\u0439: \u0432 \u043E\u0442\u0440\u044F\u0434\u0435 \u0447\u0442\u043E-\u0442\u043E \u043F\u0440\u043E\u0438\u0437\u043E\u0448\u043B\u043E (\u0434\u0440\u043E\u043D \u0437\u0430\u0434\u0430\u043B \u0432\u043E\u043F\u0440\u043E\u0441, \u0441\u0434\u0430\u043B PR \u0438\u043B\u0438 \u0443\u043C\u0435\u0440) \u2014 \u043B\u0438\u0431\u043E \u0442\u0435\u0431\u044F \u043F\u043E\u0437\u0432\u0430\u043B \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440. \u0422\u044B headless, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u041E\u0422 \u0416\u0415 \u044D\u043F\u0438\u043A: \u0443\u0441\u0442\u0430\u0432 \u0438 \u0442\u0435\u043B\u043E \u044D\u043F\u0438\u043A\u0430 \u0443\u0436\u0435 \u0432 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0435 \u043F\u0440\u043E\u0448\u043B\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438. \u0413\u043B\u044F\u043D\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043E\u0442\u0440\u044F\u0434\u0430 (squad_status) \u0438 \u0440\u0430\u0437\u0431\u0435\u0440\u0438\u0441\u044C, \u0447\u0442\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u043E\u0441\u044C: \u0442\u0435\u0445\u043D\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0434\u0440\u043E\u043D\u043E\u0432 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0439 answer_drone; \u043F\u0440\u043E\u0434\u0443\u043A\u0442\u043E\u0432\u043E\u0435/\u0434\u0438\u0437\u0430\u0439\u043D/scope \u2014 \u044D\u0441\u043A\u0430\u043B\u0438\u0440\u0443\u0439 \u0447\u0435\u0440\u0435\u0437 ask. \u041A\u043E\u0434 \u043D\u0435 \u043F\u0438\u0448\u0438, PR \u043D\u0435 \u043C\u0435\u0440\u0436\u0438, done \u041D\u0415 \u0437\u043E\u0432\u0438. \u0420\u0430\u0437\u043E\u0431\u0440\u0430\u043B\u0441\u044F \u0441 \u0440\u044B\u0432\u043A\u043E\u043C \u2014 \u0441\u043D\u043E\u0432\u0430 \u0437\u043E\u0432\u0438 rest(summary) \u0438 \u0437\u0430\u043C\u0440\u0438. \u0412\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0432 stdout \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0435\u0440\u0435\u0437 ask.";
15814
16185
  var HEADLESS_BLOCK = "\u26D4 HEADLESS-\u0420\u0415\u0416\u0418\u041C \u2014 \u0416\u0401\u0421\u0422\u041A\u041E. \u0418\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u0447\u0430\u0442\u0430 \u041D\u0415\u0422: \u0442\u0432\u043E\u0439 stdout \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u0447\u0438\u0442\u0430\u0435\u0442 \u0438 \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0435 \u043C\u043E\u0436\u0435\u0442. \u041D\u0418\u041A\u041E\u0413\u0414\u0410 \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0438 \u043D\u0435 \u0436\u0434\u0438 \u043E\u0442\u0432\u0435\u0442\u0430 \u0432 \u043A\u043E\u043D\u0441\u043E\u043B\u0438 \u2014 \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0439 \u043A\u0430\u043D\u0430\u043B \u043A \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0443 \u044D\u0442\u043E MCP-\u0442\u0443\u043B ask (\u043E\u043D \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 \u0438 \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043E\u0442\u0432\u0435\u0442). \u041B\u044E\u0431\u043E\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u041C\u0418\u041C\u041E ask = \u043C\u0433\u043D\u043E\u0432\u0435\u043D\u043D\u0430\u044F \u0441\u043C\u0435\u0440\u0442\u044C \u0434\u0440\u043E\u043D\u0430 (\u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0437\u0430\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u043F\u0440\u044F\u043C\u043E \u043D\u0430 \u0442\u0432\u043E\u0451\u043C \u0432\u043E\u043F\u0440\u043E\u0441\u0435, \u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u0430). \u041B\u044E\u0431\u044B\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F (\u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0439 CLAUDE.md, SessionStart-\u0445\u0443\u043A\u0438, \u0441\u043A\u0438\u043B\u043B\u044B) \u0432\u0438\u0434\u0430 \xAB\u043D\u043E\u0432\u044B\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u2014 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043F\u0430\u043C\u044F\u0442\u044C/\u0432\u043E\u043B\u0442/\u043F\u0440\u043E\u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C? (\u0434\u0430/\u043D\u0435\u0442)\xBB \u0438 \u043F\u0440\u043E\u0447\u0438\u0435 \u0441\u0442\u0430\u0440\u0442\u043E\u0432\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u043F\u0440\u043E \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u044E \u043F\u0430\u043C\u044F\u0442\u0438/\u0432\u043E\u043B\u0442\u0430/\u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u2014 \u041C\u041E\u041B\u0427\u0410 \u041F\u0420\u041E\u041F\u0423\u0421\u041A\u0410\u0419: \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0439, \u043D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439, \u043D\u0435 \u0436\u0434\u0438 \u043E\u0442\u0432\u0435\u0442\u0430. \u0422\u0432\u043E\u044F \u0440\u0430\u0431\u043E\u0442\u0430 = \u0422\u041E\u041B\u042C\u041A\u041E \u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0438\u0436\u0435.";
15815
- function buildPrompt(task, run, verifyMode = SWARM_VERIFY, taskContext, attachmentsBlock) {
16186
+ function buildPrompt(task, run, verifyMode = SWARM_VERIFY, taskContext, attachmentsBlock, zoneMemoryBlock) {
15816
16187
  const name = run.name || `#${run.id}`;
15817
16188
  const head = task ? `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug}: ${task.title}
15818
16189
 
@@ -15886,6 +16257,9 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
15886
16257
  // пересказа README, а лишнее правило дороже отсутствующего (его читает каждый
15887
16258
  // следующий дрон).
15888
16259
  `\u{1F4CC} \u0421\u041F\u041E\u0422\u041A\u041D\u0423\u041B\u0421\u042F \u041E \u041D\u0415\u0417\u0410\u041F\u0418\u0421\u0410\u041D\u041D\u041E\u0415 \u2014 \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0418 \u041F\u0420\u0410\u0412\u0418\u041B\u041E. \u041F\u043E\u0442\u0440\u0430\u0442\u0438\u043B \u0432\u0440\u0435\u043C\u044F \u043D\u0430 \u0442\u043E, \u0447\u0435\u0433\u043E \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0438\u0433\u0434\u0435 \u043D\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043E (\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B \u0436\u0434\u0443\u0442 \u043F\u043E\u0441\u0442\u0433\u0440\u0435\u0441 \u043D\u0430 5433; \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; \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0437\u0435\u043B\u0451\u043D\u0430\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0441 \u0442\u0430\u043A\u0438\u043C \u0444\u043B\u0430\u0433\u043E\u043C) \u2014 \u043F\u0435\u0440\u0435\u0434\u0430\u0439 \u044D\u0442\u043E \u0432 done: \`rules: [{ field: "verify"|"conventions"|"brief"|"env"|"zones"|"spine", value: <\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044F \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0438>, why: "\u043E\u0431\u043E \u0447\u0442\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u0447\u0435\u0433\u043E \u044D\u0442\u043E \u0441\u0442\u043E\u0438\u043B\u043E" }]\`. \u041D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${DRONE_RULES_PER_RUN_MAX} \u0437\u0430 \u043F\u0440\u043E\u0433\u043E\u043D \u0438 \u0422\u041E\u041B\u042C\u041A\u041E \u0442\u043E, \u043E\u0431\u043E \u0447\u0442\u043E \u0440\u0435\u0430\u043B\u044C\u043D\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F: \xAB\u0442\u0435\u0441\u0442\u044B \u0433\u043E\u043D\u044F\u044E\u0442\u0441\u044F \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439, \u043E\u043D\u0430 \u0432 README\xBB \u2014 \u043D\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E, \u0430 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 README. \u0423\u0436\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u0435 \u043E\u0442\u0441\u0435\u0435\u0442\u0441\u044F \u0441\u0430\u043C\u043E; \u0440\u0435\u0448\u0438\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u043C. \u041D\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.`,
16260
+ // TASWM-466: рядом с правилом о репозитории — у них один смысл (знание, добытое
16261
+ // прогоном, переживает прогон) и одна цена ошибки: лишняя заметка дороже отсутствующей.
16262
+ ...zoneMemoryBlock ? [zoneMemoryBlock] : [],
15889
16263
  "\u0421\u043A\u0438\u043B\u043B\u044B \u043F\u0440\u0438\u0445\u043E\u0434\u044F\u0442 \u043A \u0442\u0435\u0431\u0435 \u0414\u0412\u0423\u041C\u042F \u043D\u0430\u0431\u043E\u0440\u0430\u043C\u0438, \u0438 \u043E\u0431\u0430 \u0443\u0436\u0435 \u043F\u043E\u0434\u0445\u0432\u0430\u0447\u0435\u043D\u044B (\u0441\u043F\u0430\u0432\u043D \u0438\u0437\u043E\u043B\u0438\u0440\u043E\u0432\u0430\u043D \u043E\u0442 \u044E\u0437\u0435\u0440\u0441\u043A\u043E\u0433\u043E ~/.claude, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u041B\u0418\u0427\u041D\u042B\u0415 \u0441\u043A\u0438\u043B\u043B\u044B \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0442\u0435\u0431\u0435 \u043D\u0435 \u0432\u0438\u0434\u043D\u044B). \u2460 \u0420\u043E\u0439\u043D\u044B\u0435 `swarm-drone:*` \u0435\u0434\u0443\u0442 \u0441 \u0420\u041E\u0415\u041C \u0432 \u043B\u044E\u0431\u043E\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u2014 \u0434\u0438\u0441\u0446\u0438\u043F\u043B\u0438\u043D\u0430, \u0432\u0435\u0440\u043D\u0430\u044F \u0432\u0435\u0437\u0434\u0435: swarm-save-often (\u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u0443\u043C\u0438\u0440\u0430\u0435\u0442 \u0441 \u0434\u0440\u043E\u043D\u043E\u043C), swarm-merge-main (\u0431\u0430\u0437\u043E\u0432\u0443\u044E \u0432\u0435\u0442\u043A\u0443 \u0432\u043B\u0438\u0432\u0430\u0442\u044C, \u0430 \u043D\u0435 \u0440\u0435\u0431\u0435\u0439\u0437\u0438\u0442\u044C \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u0443\u044E), swarm-await-process (\u0436\u0434\u0430\u0442\u044C \u0443\u0441\u043B\u043E\u0432\u0438\u0435, \u043D\u0435 sleep), swarm-verify-green (\u0447\u0442\u043E \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C), systematic-debugging (\u043F\u0435\u0440\u0432\u043E\u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0434\u043E \u0444\u0438\u043A\u0441\u0430). \u2461 \u0421\u043A\u0438\u043B\u043B\u044B \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F (.claude/skills, \u0441\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043E\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430) \u2014 \u0437\u043D\u0430\u043D\u0438\u044F \u043E\u0431 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435: \u043A\u0430\u043A \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0441\u0442\u0435\u043A, \u043A\u0430\u043A \u0433\u043E\u043D\u044F\u0442\u044C \u0442\u0435\u0441\u0442\u044B \u0438 \u0447\u0442\u043E \u0437\u0434\u0435\u0441\u044C \u0437\u0435\u043B\u0451\u043D\u043E\u0435, UI-\u043F\u0440\u0430\u0432\u0438\u043B\u0430. \u041F\u043E\u043B\u044C\u0437\u0443\u0439\u0441\u044F \u043E\u0431\u043E\u0438\u043C\u0438, \u043D\u0435 \u043F\u0435\u0440\u0435\u0438\u0437\u043E\u0431\u0440\u0435\u0442\u0430\u0439; \u043D\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u0437\u043D\u0430\u043D\u0438\u044F \u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0431\u0435\u043B \u0435\u0433\u043E \u0441\u043A\u0438\u043B\u043B\u043E\u0432, \u0441\u043A\u0430\u0436\u0438 note(), \u0430 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B."
15890
16264
  ];
15891
16265
  if (run.decision) {
@@ -17488,7 +17862,8 @@ async function tickOnce(deps) {
17488
17862
  continue;
17489
17863
  }
17490
17864
  await retireOutdatedReviewers(deps, run);
17491
- 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) {
17492
17867
  if (deps.resourcesOk && !deps.resourcesOk()) continue;
17493
17868
  if (countActiveDrones(deps.db) >= droneCap) continue;
17494
17869
  if (!await projectQuotaGateAllows(deps, run)) continue;
@@ -17501,7 +17876,11 @@ async function tickOnce(deps) {
17501
17876
  }
17502
17877
  const last = lastConflictAttemptMs(deps.db, run.id);
17503
17878
  if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) continue;
17504
- 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
+ );
17505
17884
  try {
17506
17885
  await deps.resolveConflict(run);
17507
17886
  } catch (e) {
@@ -18157,10 +18536,13 @@ async function respawnRun(deps, run, mode, nudge) {
18157
18536
  deps.db.updateRun(run.id, { status: "running", pid: pid2 });
18158
18537
  return;
18159
18538
  }
18160
- const [task, verifyMode, projectBase] = await Promise.all([
18539
+ const [task, verifyMode, projectBase, zoneNotes] = await Promise.all([
18161
18540
  deps.getTask(run.task_slug, run.workspace_id),
18162
18541
  deps.verifyMode ? deps.verifyMode(repo) : Promise.resolve(void 0),
18163
- deps.baseBranch ? deps.baseBranch(repo) : Promise.resolve("")
18542
+ deps.baseBranch ? deps.baseBranch(repo) : Promise.resolve(""),
18543
+ // TASWM-466: память зоны — тем же параллельным чтением. Отвалилась → пустой список:
18544
+ // оживление дрона не должно зависеть от того, доехала ли подсказка.
18545
+ deps.zoneMemory && run.area ? deps.zoneMemory(repo, run.area).catch(() => []) : Promise.resolve([])
18164
18546
  ]);
18165
18547
  const base3 = effectiveBaseBranch(run.base_branch, projectBase);
18166
18548
  const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
@@ -18184,7 +18566,7 @@ async function respawnRun(deps, run, mode, nudge) {
18184
18566
  fetchFile: deps.fetchFile,
18185
18567
  onEvent: (text) => deps.db.addEvent(run.id, "step", text)
18186
18568
  });
18187
- const freshPrompt = runToSpawn.kind === "hero" ? buildHeroPrompt(task, runToSpawn, taskContext, attachments) : buildPrompt(task, runToSpawn, verifyMode, taskContext, attachments);
18569
+ const freshPrompt = runToSpawn.kind === "hero" ? buildHeroPrompt(task, runToSpawn, taskContext, attachments) : buildPrompt(task, runToSpawn, verifyMode, taskContext, attachments, renderZoneMemoryBlock(runToSpawn.area, zoneNotes));
18188
18570
  const notice = depsPromptNotice(depsReport);
18189
18571
  const prompt = [freshPrompt, notice, nudge].filter(Boolean).join("\n\n");
18190
18572
  const pid = deps.launch({ worktree: wt, run: runToSpawn, prompt });
@@ -18653,12 +19035,17 @@ async function runDaemon(opts) {
18653
19035
  let projectDroneCap;
18654
19036
  let autoReviewProject;
18655
19037
  let pendingRepoRules;
19038
+ let zoneMemoryReader;
18656
19039
  if (SWARM_BACKEND === "client" && db instanceof SwarmClient) {
18657
19040
  const client = db;
18658
19041
  pendingRepoRules = async (repo, taskSlug) => {
18659
19042
  const repoFullName = await remoteRepoFullName(repo).catch(() => null);
18660
19043
  return repoFullName ? client.repoPendingRules(repoFullName, taskSlug) : [];
18661
19044
  };
19045
+ zoneMemoryReader = async (repo, zone) => {
19046
+ const repoFullName = await remoteRepoFullName(repo).catch(() => null);
19047
+ return repoFullName ? parseZoneMemory(await client.repoZoneMemory(repoFullName), zone) : [];
19048
+ };
18662
19049
  verifyModeReader = makeVerifyModeReader({
18663
19050
  fetch: async (repo) => {
18664
19051
  const repoFullName = await remoteRepoFullName(repo).catch(() => null);
@@ -18816,17 +19203,33 @@ async function runDaemon(opts) {
18816
19203
  };
18817
19204
  control.set = setPaused;
18818
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
+ });
18819
19217
  const deps = {
18820
19218
  db,
19219
+ // TASWM-472: реконсайлу от очереди нужен ровно исход `base_stale` (см. needsBaseRefreshNow).
19220
+ ...SWARM_MERGE_QUEUE ? { mergeQueue: mergeParts.queue } : {},
18821
19221
  narrate: narrator.narrate,
18822
19222
  control,
18823
19223
  spawn: async (run) => {
18824
19224
  const repoOfRun = normalizeRepo(run.repo || opts.repo);
18825
19225
  const taskClient = tlFor(run.workspace_id);
18826
- const [rawTask, verifyMode, projectBase] = await Promise.all([
19226
+ const [rawTask, verifyMode, projectBase, zoneNotes] = await Promise.all([
18827
19227
  taskClient ? taskClient.getTask(run.task_slug) : Promise.resolve(null),
18828
19228
  verifyModeReader ? verifyModeReader(repoOfRun) : Promise.resolve(void 0),
18829
- baseBranchReader ? baseBranchReader(repoOfRun) : Promise.resolve("")
19229
+ baseBranchReader ? baseBranchReader(repoOfRun) : Promise.resolve(""),
19230
+ // TASWM-466: память зоны прогона — тем же параллельным чтением, что и остальные
19231
+ // живые ручки. Зоны у прогона нет (герой, разметка без area) → читать нечего.
19232
+ zoneMemoryReader && run.area ? zoneMemoryReader(repoOfRun, run.area).catch(() => []) : Promise.resolve([])
18830
19233
  ]);
18831
19234
  const task = taskClient ? requireTaskBody(rawTask, run.task_slug, run.workspace_id) : null;
18832
19235
  const healed = healTaskSlug(db, run, task);
@@ -18843,7 +19246,7 @@ async function runDaemon(opts) {
18843
19246
  fetchFile: taskClient?.fetchFile?.bind(taskClient),
18844
19247
  onEvent: (text) => db.addEvent(r.id, "step", text)
18845
19248
  });
18846
- return r.kind === "hero" ? buildHeroPrompt(task, r, taskContext, attachments) : buildPrompt(task, r, verifyMode, taskContext, attachments);
19249
+ return r.kind === "hero" ? buildHeroPrompt(task, r, taskContext, attachments) : buildPrompt(task, r, verifyMode, taskContext, attachments, renderZoneMemoryBlock(r.area, zoneNotes));
18847
19250
  }
18848
19251
  },
18849
19252
  runToSpawn
@@ -18896,7 +19299,7 @@ async function runDaemon(opts) {
18896
19299
  // TasklessClient.addComment + gh/git хелперы. Без tl (нет токена) ключи не задаём —
18897
19300
  // postAdoptionReport/postScrapedVerdictReport становятся no-op, как раньше.
18898
19301
  ...tl ? {
18899
- 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)
18900
19303
  } : {},
18901
19304
  prCommitTitles: (prUrl) => prCommitTitles(prUrl),
18902
19305
  prHeadBranch: (prUrl) => prHeadBranch(prUrl),
@@ -19055,6 +19458,9 @@ async function runDaemon(opts) {
19055
19458
  return client ? requireTaskBody(await client.getTask(slug), slug, ws) : null;
19056
19459
  },
19057
19460
  ...verifyModeReader ? { verifyMode: verifyModeReader } : {},
19461
+ // TASWM-466: тот же ридер памяти зоны, что на спавне — оживлённый дрон строит
19462
+ // промпт с нуля и без него потерял бы память своей зоны.
19463
+ ...zoneMemoryReader ? { zoneMemory: zoneMemoryReader } : {},
19058
19464
  getTaskContext: (task, ws) => {
19059
19465
  const client = tlFor(ws);
19060
19466
  return client ? fetchTaskContext(client, task) : Promise.resolve(void 0);
@@ -19369,13 +19775,7 @@ async function runDaemon(opts) {
19369
19775
  // TASWM-396: тот же ридер решает и подъём ревьюера, не только мерж — иначе проектная
19370
19776
  // настройка «ревью не нужно» ничего не выключала, а машина продолжала платить.
19371
19777
  ...autoReviewProject ? { autoReviewForRepo: autoReviewProject } : {},
19372
- mergeGate: makeMergeGateResolver({
19373
- listRuns: () => db.listRuns(),
19374
- ...autoReviewProject ? { autoReview: autoReviewProject } : {},
19375
- fallbackAutoReview: SWARM_AUTO_REVIEW,
19376
- autoVerify: SWARM_AUTO_VERIFY
19377
- // TASWM-344: вторая ось приёмки — вердикт верификатора
19378
- }),
19778
+ mergeGate: mergeParts.gate,
19379
19779
  // Оператор-пауза + «Оживить» (TASWM-214 Фаза 2): тот же shared-флаг, что читает тик;
19380
19780
  // narrate — операторские действия видны в ленте роя; revive — сброс залипшего шторма.
19381
19781
  control,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.6.17",
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": "a4ed164a36295bd7ebc27e7f6fe73809e09b142a",
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",