@taskless-app/swarm 0.6.10 → 0.6.12

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 +1220 -356
  2. package/dist/overmind.js +1128 -301
  3. package/package.json +2 -2
package/dist/overmind.js CHANGED
@@ -13,7 +13,7 @@ function isMilestoneKind(kind) {
13
13
  return MILESTONE_KIND_SET.has(kind);
14
14
  }
15
15
  function toRunKind(role) {
16
- return role === "hero" || role === "reviewer" ? role : "drone";
16
+ return role === "hero" || role === "reviewer" || role === "verifier" ? role : "drone";
17
17
  }
18
18
  var ACTIVE_DRONE_STATUSES, EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
19
19
  var init_types = __esm({
@@ -335,6 +335,53 @@ var init_reviewPhase = __esm({
335
335
  }
336
336
  });
337
337
 
338
+ // ../swarm-core/src/verifyVerdict.ts
339
+ function isVerifyVerdict(v) {
340
+ return typeof v === "string" && VERIFY_VERDICT_SET.has(v);
341
+ }
342
+ function toVerifyVerdict(v) {
343
+ return isVerifyVerdict(v) ? v : null;
344
+ }
345
+ function isNegativeVerifyVerdict(v) {
346
+ return v === "not_reproduced" || v === "found_issues";
347
+ }
348
+ function verifyVerdictLabel(v) {
349
+ switch (v) {
350
+ case "confirmed":
351
+ return "\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E";
352
+ case "not_reproduced":
353
+ return "\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F";
354
+ case "found_issues":
355
+ return "\u041D\u0410\u0428\u0401\u041B";
356
+ }
357
+ }
358
+ function parseVerifyVerdictComment(text) {
359
+ const line = (text ?? "").split("\n").find((l) => l.trim().length > 0);
360
+ if (!line) return null;
361
+ let best = null;
362
+ for (const [verdict, marks] of VERDICT_MARKS) {
363
+ for (const mark of marks) {
364
+ const at = line.indexOf(mark);
365
+ if (at < 0) continue;
366
+ if (!best || at < best.at) best = { verdict, at };
367
+ }
368
+ }
369
+ return best?.verdict ?? null;
370
+ }
371
+ var VERIFY_VERDICTS, VERIFY_VERDICT_SET, VERDICT_MARKS;
372
+ var init_verifyVerdict = __esm({
373
+ "../swarm-core/src/verifyVerdict.ts"() {
374
+ "use strict";
375
+ VERIFY_VERDICTS = ["confirmed", "not_reproduced", "found_issues"];
376
+ VERIFY_VERDICT_SET = new Set(VERIFY_VERDICTS);
377
+ VERDICT_MARKS = [
378
+ ["not_reproduced", ["\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F", "NOT REPRODUCED", "\u041D\u0415 \u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E", "NOT CONFIRMED"]],
379
+ ["found_issues", ["\u041D\u0410\u0428\u0401\u041B", "\u041D\u0410\u0428\u0415\u041B", "FOUND"]],
380
+ ["confirmed", ["\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E", "CONFIRMED"]]
381
+ ];
382
+ }
383
+ });
384
+
338
385
  // ../swarm-core/src/mergeGate.ts
339
386
  function computeMergeGate(input) {
340
387
  if (!input.hasRun) {
@@ -343,21 +390,38 @@ function computeMergeGate(input) {
343
390
  if (!input.autoReview) {
344
391
  return { allowed: true, code: "auto_review_off", reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F" };
345
392
  }
346
- if (input.reviewVerdict === "approve") {
347
- return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
348
- }
349
393
  if (input.reviewVerdict === "changes") {
350
394
  return { allowed: false, code: "changes_requested", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u043F\u0440\u0430\u0432\u043A\u0438 (REQUEST CHANGES) \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F, \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043D\u043E\u0432\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430" };
351
395
  }
352
- return {
353
- allowed: false,
354
- code: "no_verdict",
355
- reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E, \u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-355)"
356
- };
396
+ if (input.reviewVerdict !== "approve") {
397
+ return {
398
+ allowed: false,
399
+ code: "no_verdict",
400
+ reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E, \u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-355)"
401
+ };
402
+ }
403
+ if (input.autoVerify) {
404
+ if (input.verifyVerdict == null) {
405
+ return {
406
+ allowed: false,
407
+ code: "no_verify_verdict",
408
+ reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve, \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0433\u043E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0435\u0439 \u043F\u0440\u0438\u0451\u043C\u043A\u0443 (TASWM-344)"
409
+ };
410
+ }
411
+ if (isNegativeVerifyVerdict(input.verifyVerdict)) {
412
+ return {
413
+ allowed: false,
414
+ code: "verify_negative",
415
+ reason: `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0432\u044B\u043D\u0435\u0441 \xAB${verifyVerdictLabel(input.verifyVerdict)}\xBB \u2014 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043B\u043E\u0441\u044C, \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-344)`
416
+ };
417
+ }
418
+ }
419
+ return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
357
420
  }
358
421
  var init_mergeGate = __esm({
359
422
  "../swarm-core/src/mergeGate.ts"() {
360
423
  "use strict";
424
+ init_verifyVerdict();
361
425
  }
362
426
  });
363
427
 
@@ -1383,6 +1447,7 @@ var init_src = __esm({
1383
1447
  init_timeline();
1384
1448
  init_reviewPhase();
1385
1449
  init_mergeGate();
1450
+ init_verifyVerdict();
1386
1451
  init_status();
1387
1452
  init_deps();
1388
1453
  init_time();
@@ -1532,11 +1597,18 @@ var init_swarmFiles = __esm({
1532
1597
  });
1533
1598
 
1534
1599
  // src/pricing.ts
1535
- function costFromUsage(usage, model, table = MODEL_PRICING) {
1536
- const pricing = model ? table[model] : void 0;
1537
- if (!pricing) {
1600
+ function pricingAt(pricing, at) {
1601
+ const intro = pricing.intro;
1602
+ if (!intro) return pricing;
1603
+ const day = at.toISOString().slice(0, 10);
1604
+ return day <= intro.until ? { inputPerMTok: intro.inputPerMTok, outputPerMTok: intro.outputPerMTok } : pricing;
1605
+ }
1606
+ function costFromUsage(usage, model, table = MODEL_PRICING, at = /* @__PURE__ */ new Date()) {
1607
+ const entry = model ? table[model] : void 0;
1608
+ if (!entry) {
1538
1609
  return { costUsd: 0, warning: `\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u043F\u0440\u0430\u0439\u0441\u0438\u043D\u0433\u0430: "${model ?? ""}" \u2014 cost \u043D\u0435 \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u043D` };
1539
1610
  }
1611
+ const pricing = pricingAt(entry, at);
1540
1612
  const input = usage.input_tokens ?? 0;
1541
1613
  const output = usage.output_tokens ?? 0;
1542
1614
  const cacheWrite = usage.cache_creation_input_tokens ?? 0;
@@ -1549,10 +1621,12 @@ var init_pricing = __esm({
1549
1621
  "src/pricing.ts"() {
1550
1622
  "use strict";
1551
1623
  MODEL_PRICING = {
1552
- // Вводная цена Sonnet 5 действует до 31.08.2026, после чего вернётся к 3/15.
1553
- // Дата не автоматизируется намеренно: одна строка раз в жизни дешевле, чем
1554
- // ветвление по времени внутри расчёта стоимости.
1555
- "claude-sonnet-5": { inputPerMTok: 2, outputPerMTok: 10 },
1624
+ // Sonnet 5: вводные 2/10 по 31.08.2026 включительно, дальше базовые 3/15.
1625
+ "claude-sonnet-5": {
1626
+ inputPerMTok: 3,
1627
+ outputPerMTok: 15,
1628
+ intro: { inputPerMTok: 2, outputPerMTok: 10, until: "2026-08-31" }
1629
+ },
1556
1630
  "claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
1557
1631
  // Прогоны на 4.8 остаются в истории — строку держим ради их стоимости.
1558
1632
  "claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
@@ -1991,6 +2065,7 @@ __export(config_exports, {
1991
2065
  SWARM_ANOMALY_RESOURCE_GATE_STALL_MS: () => SWARM_ANOMALY_RESOURCE_GATE_STALL_MS,
1992
2066
  SWARM_ANOMALY_RUN_DOWN_WINDOW_MS: () => SWARM_ANOMALY_RUN_DOWN_WINDOW_MS,
1993
2067
  SWARM_AUTO_REVIEW: () => SWARM_AUTO_REVIEW,
2068
+ SWARM_AUTO_VERIFY: () => SWARM_AUTO_VERIFY,
1994
2069
  SWARM_BACKEND: () => SWARM_BACKEND,
1995
2070
  SWARM_CLAIM_INTERVAL_MS: () => SWARM_CLAIM_INTERVAL_MS,
1996
2071
  SWARM_CPU_SANITY_BUSY: () => SWARM_CPU_SANITY_BUSY,
@@ -2121,7 +2196,7 @@ function reviewCutoffAt() {
2121
2196
  }
2122
2197
  return at;
2123
2198
  }
2124
- 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, 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, 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_WATCHDOG_STALL_MS;
2199
+ 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, 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_WATCHDOG_STALL_MS;
2125
2200
  var init_config = __esm({
2126
2201
  "src/config.ts"() {
2127
2202
  "use strict";
@@ -2192,6 +2267,7 @@ var init_config = __esm({
2192
2267
  TASKLESS_TASK_URL_BASE = process.env.TASKLESS_TASK_URL_BASE || (TASKLESS_WORKSPACE_SLUG ? `${TASKLESS_API.replace(/\/$/, "")}/app/${TASKLESS_WORKSPACE_SLUG}` : "");
2193
2268
  SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
2194
2269
  SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
2270
+ SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
2195
2271
  REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join5(SWARM_HOME, "review-cutoff.json");
2196
2272
  SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
2197
2273
  SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
@@ -2631,6 +2707,105 @@ var init_heartbeat = __esm({
2631
2707
  }
2632
2708
  });
2633
2709
 
2710
+ // src/agentToken.ts
2711
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
2712
+ import { join as join8 } from "path";
2713
+ function loadAgentToken() {
2714
+ try {
2715
+ const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
2716
+ return s.token ? s : null;
2717
+ } catch {
2718
+ return null;
2719
+ }
2720
+ }
2721
+ async function probeAgentToken(args, fetchFn = fetch) {
2722
+ try {
2723
+ const res = await fetchFn(`${base(args.api)}/api/auth/me`, {
2724
+ headers: { Authorization: `Bearer ${args.token}` },
2725
+ // Оборвётся по таймауту — попадём в catch ниже, то есть в "unreachable": это ровно
2726
+ // то, чем висящая сеть и является. Соседи по CLI (daemonLink) живут так же.
2727
+ signal: AbortSignal.timeout(args.timeoutMs ?? TOKEN_PROBE_TIMEOUT_MS)
2728
+ });
2729
+ if (res.status === 401 || res.status === 403) return "revoked";
2730
+ return res.ok ? "ok" : "unreachable";
2731
+ } catch {
2732
+ return "unreachable";
2733
+ }
2734
+ }
2735
+ function isRevokedTokenError(e) {
2736
+ if (e && typeof e === "object" && e.code === 401) return true;
2737
+ const text = e instanceof Error ? e.message : typeof e === "string" ? e : "";
2738
+ return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
2739
+ }
2740
+ function revokedTokenLine() {
2741
+ return "[taskless] \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (401 invalid_token) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443. \u041F\u043E\u0447\u0438\u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: swarm login";
2742
+ }
2743
+ function revokedTokenComplaint() {
2744
+ return [
2745
+ "",
2746
+ "\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
2747
+ "\u2502 \u0422\u041E\u041A\u0415\u041D \u0420\u041E\u042F \u041D\u0415 \u041F\u0420\u0418\u041D\u042F\u0422 (401). \u0414\u043E\u0441\u0442\u0443\u043F \u043A Taskless \u0440\u0430\u0437\u043E\u0440\u0432\u0430\u043D. \u2502",
2748
+ "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
2749
+ "\u041F\u0440\u0438\u0447\u0438\u043D\u0430 \u2014 \u043E\u0434\u043D\u0430 \u0438\u0437 \u0442\u0440\u0451\u0445, \u0432\u0441\u0435 \u0442\u0440\u0438 \u0440\u0435\u0448\u0430\u044E\u0442\u0441\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C:",
2750
+ " \u2022 \xAB\u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u044D\u0442\u043E\u0442 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\xBB \u0432 \u043F\u0430\u0440\u043A\u0435 \u043C\u0430\u0448\u0438\u043D \u2014 \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u0442\u043E\u043A\u0435\u043D \u042D\u0422\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u044B;",
2751
+ " \u2022 \xAB\u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F \u0440\u043E\u044F\xBB \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 (\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439) \u2014 \u043E\u0442\u043E\u0437\u0432\u0430\u043D\u044B \u0432\u0441\u0435;",
2752
+ " \u2022 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u043C\u0430\u0441\u0442\u0435\u0440-\u0442\u0443\u043C\u0431\u043B\u0435\u0440\u043E\u043C \u2014 \u0442\u043E\u043A\u0435\u043D \u0430\u0433\u0435\u043D\u0442\u0430 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u043E\u0432\u0430\u0442\u044C.",
2753
+ "",
2754
+ "\u0412\u0445\u043E\u0434 \u043D\u0430 \u0414\u0420\u0423\u0413\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u0435 \u044D\u0442\u043E\u0442 \u0442\u043E\u043A\u0435\u043D \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0433\u0430\u0441\u0438\u0442 (TASWM-324): \u0443 \u043A\u0430\u0436\u0434\u043E\u0439",
2755
+ "\u043C\u0430\u0448\u0438\u043D\u044B \u0441\u0432\u043E\u0439 \u0442\u043E\u043A\u0435\u043D, \u043F\u0440\u0438\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 \u043A \u0435\u0451 hostId.",
2756
+ "",
2757
+ "\u0414\u0435\u043C\u043E\u043D \u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E: \u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442, \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043D\u0435 \u0431\u0435\u0440\u0443\u0442\u0441\u044F \u0438 \u043E\u0442\u0447\u0451\u0442\u044B",
2758
+ "\u043D\u0435 \u043F\u0438\u0448\u0443\u0442\u0441\u044F. \u041F\u043E\u0447\u0438\u043D\u0438 \u0432\u0445\u043E\u0434\u043E\u043C \u0437\u0430\u043D\u043E\u0432\u043E: swarm login",
2759
+ "\u041F\u0440\u043E\u0431\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442, \u0430 \u0432\u043E\u0439\u0442\u0438 \u043C\u0435\u0448\u0430\u0435\u0442 \u0441\u0442\u0430\u0440\u044B\u0439 \u0444\u0430\u0439\u043B: swarm login --force",
2760
+ ""
2761
+ ].join("\n");
2762
+ }
2763
+ var STORE_PATH, base, TOKEN_PROBE_TIMEOUT_MS;
2764
+ var init_agentToken = __esm({
2765
+ "src/agentToken.ts"() {
2766
+ "use strict";
2767
+ init_config();
2768
+ STORE_PATH = join8(SWARM_HOME, "agent.json");
2769
+ base = (api) => api.replace(/\/$/, "");
2770
+ TOKEN_PROBE_TIMEOUT_MS = 8e3;
2771
+ }
2772
+ });
2773
+
2774
+ // src/linkFailure.ts
2775
+ function describeError(e, maxDepth = 4) {
2776
+ const parts = [];
2777
+ const seen = /* @__PURE__ */ new Set();
2778
+ let cur = e;
2779
+ for (let depth = 0; depth < maxDepth && cur != null && !seen.has(cur); depth++) {
2780
+ seen.add(cur);
2781
+ const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
2782
+ const code = cur.code;
2783
+ const codeText = code === void 0 || code === null ? "" : String(code);
2784
+ const label = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2785
+ if (label && !parts.includes(label)) parts.push(label);
2786
+ cur = cur.cause;
2787
+ }
2788
+ return parts.join(" \u2190 ") || String(e);
2789
+ }
2790
+ function explainLinkFailure(e) {
2791
+ const detail = describeError(e);
2792
+ if (detail.includes(UNKNOWN_LINK_REASON)) return { kind: "unknown", detail };
2793
+ if (isRevokedTokenError(e) || isRevokedTokenError(detail)) return { kind: "unauthorized", detail };
2794
+ if (MISSING_TOOL_RE.test(detail)) return { kind: "missing_tool", detail };
2795
+ if (OFFLINE_RE.test(detail)) return { kind: "offline", detail };
2796
+ return { kind: "rejected", detail };
2797
+ }
2798
+ var UNKNOWN_LINK_REASON, OFFLINE_RE, MISSING_TOOL_RE;
2799
+ var init_linkFailure = __esm({
2800
+ "src/linkFailure.ts"() {
2801
+ "use strict";
2802
+ init_agentToken();
2803
+ UNKNOWN_LINK_REASON = "\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u043D\u0435\u0442, \u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B";
2804
+ OFFLINE_RE = /fetch failed|econnreset|econnrefused|econnaborted|enotfound|eai_again|etimedout|ehostunreach|enetunreach|epipe|socket hang up|und_err|network error|connection (?:closed|refused|reset|error)|connect timeout/i;
2805
+ MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
2806
+ }
2807
+ });
2808
+
2634
2809
  // src/procList.ts
2635
2810
  function parsePsRows(out) {
2636
2811
  const rows = [];
@@ -2952,7 +3127,7 @@ var init_procCleanup = __esm({
2952
3127
  // src/git.ts
2953
3128
  import { existsSync as existsSync9, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
2954
3129
  import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
2955
- import { isAbsolute as isAbsolute2, join as join15, sep as sep2 } from "path";
3130
+ import { isAbsolute as isAbsolute2, join as join16, sep as sep2 } from "path";
2956
3131
  import { $ as $2 } from "execa";
2957
3132
  function isUnrecoverableRefError(e) {
2958
3133
  if (e instanceof MissingRefError) return true;
@@ -2964,7 +3139,7 @@ async function reclaimStaleWorktree(repo, worktree) {
2964
3139
  }
2965
3140
  function isLinkedWorktree(dir) {
2966
3141
  try {
2967
- return statSync7(join15(dir, ".git")).isFile();
3142
+ return statSync7(join16(dir, ".git")).isFile();
2968
3143
  } catch {
2969
3144
  return false;
2970
3145
  }
@@ -3074,8 +3249,8 @@ async function ensureGitExcluded(worktree, pattern) {
3074
3249
  try {
3075
3250
  const commonDir = await gitPath(worktree, "--git-common-dir");
3076
3251
  if (!commonDir) return false;
3077
- const infoDir = join15(commonDir, "info");
3078
- const excludeFile = join15(infoDir, "exclude");
3252
+ const infoDir = join16(commonDir, "info");
3253
+ const excludeFile = join16(infoDir, "exclude");
3079
3254
  let current = "";
3080
3255
  try {
3081
3256
  current = await readFile(excludeFile, "utf8");
@@ -3095,7 +3270,7 @@ async function gitPath(worktree, flag) {
3095
3270
  const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
3096
3271
  const raw = out.stdout.trim();
3097
3272
  if (out.exitCode !== 0 || !raw) return null;
3098
- return isAbsolute2(raw) ? raw : join15(worktree, raw);
3273
+ return isAbsolute2(raw) ? raw : join16(worktree, raw);
3099
3274
  }
3100
3275
  async function ensureWorktreeHygiene(worktree) {
3101
3276
  for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
@@ -3105,12 +3280,12 @@ async function installGuardHooks(worktree) {
3105
3280
  try {
3106
3281
  const gitDir = await gitPath(worktree, "--git-dir");
3107
3282
  if (!gitDir) return false;
3108
- const hooksDir = join15(gitDir, "swarm-hooks");
3283
+ const hooksDir = join16(gitDir, "swarm-hooks");
3109
3284
  const prev = await previousHooksPath(worktree);
3110
3285
  await mkdir(hooksDir, { recursive: true });
3111
3286
  for (const hook of GUARD_HOOKS) {
3112
- await writeFile(join15(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
3113
- await chmod(join15(hooksDir, hook), 493);
3287
+ await writeFile(join16(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
3288
+ await chmod(join16(hooksDir, hook), 493);
3114
3289
  }
3115
3290
  await $2`git -C ${worktree} config extensions.worktreeConfig true`;
3116
3291
  await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
@@ -3129,7 +3304,7 @@ async function previousHooksPath(worktree) {
3129
3304
  if (out.exitCode === 0 && value) return value;
3130
3305
  }
3131
3306
  const commonDir = await gitPath(worktree, "--git-common-dir");
3132
- return commonDir ? join15(commonDir, "hooks") : null;
3307
+ return commonDir ? join16(commonDir, "hooks") : null;
3133
3308
  }
3134
3309
  async function removeWorktree(repo, path) {
3135
3310
  const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
@@ -3148,7 +3323,7 @@ async function worktreeDirty(path) {
3148
3323
  async function repairMovedWorktrees(targetDir) {
3149
3324
  const subdirs = (dir) => {
3150
3325
  try {
3151
- return readdirSync10(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join15(dir, d.name));
3326
+ return readdirSync10(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join16(dir, d.name));
3152
3327
  } catch {
3153
3328
  return [];
3154
3329
  }
@@ -3275,18 +3450,18 @@ __export(droneChannel_exports, {
3275
3450
  verifyLocalCliToken: () => verifyLocalCliToken
3276
3451
  });
3277
3452
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3278
- import { chmodSync, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
3453
+ import { chmodSync, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
3279
3454
  import { dirname as dirname3 } from "path";
3280
3455
  import { serve } from "@hono/node-server";
3281
3456
  function loadChannelSecret(path) {
3282
3457
  try {
3283
- const s = readFileSync13(path, "utf8").trim();
3458
+ const s = readFileSync14(path, "utf8").trim();
3284
3459
  if (s.length >= 32) return s;
3285
3460
  } catch {
3286
3461
  }
3287
3462
  const secret = randomBytes(32).toString("hex");
3288
3463
  mkdirSync7(dirname3(path), { recursive: true });
3289
- writeFileSync9(path, secret, { mode: 384 });
3464
+ writeFileSync10(path, secret, { mode: 384 });
3290
3465
  try {
3291
3466
  chmodSync(path, 384);
3292
3467
  } catch {
@@ -3616,7 +3791,7 @@ var init_droneChannel = __esm({
3616
3791
 
3617
3792
  // src/attachments.ts
3618
3793
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
3619
- import { join as join18 } from "path";
3794
+ import { join as join19 } from "path";
3620
3795
  function attachmentsFromDoc(doc, origin) {
3621
3796
  const out = [];
3622
3797
  const walk2 = (node) => {
@@ -3707,7 +3882,7 @@ async function materializeAttachments(opts) {
3707
3882
  };
3708
3883
  const result = { files: [], skipped: [] };
3709
3884
  if (opts.refs.length === 0) return result;
3710
- const dir = join18(opts.worktree, ATTACHMENTS_DIR);
3885
+ const dir = join19(opts.worktree, ATTACHMENTS_DIR);
3711
3886
  await io.removeDir(dir);
3712
3887
  let dirReady = false;
3713
3888
  let total = 0;
@@ -3753,7 +3928,7 @@ async function materializeAttachments(opts) {
3753
3928
  await io.mkdir(dir);
3754
3929
  dirReady = true;
3755
3930
  }
3756
- await io.writeFile(join18(dir, name), file.bytes);
3931
+ await io.writeFile(join19(dir, name), file.bytes);
3757
3932
  } catch (e) {
3758
3933
  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)})`);
3759
3934
  continue;
@@ -3906,7 +4081,7 @@ var init_attachments = __esm({
3906
4081
 
3907
4082
  // src/shotUpload.ts
3908
4083
  import { readFile as fsReadFile } from "fs/promises";
3909
- import { basename as basename2, isAbsolute as isAbsolute3, join as join19 } from "path";
4084
+ import { basename as basename2, isAbsolute as isAbsolute3, join as join20 } from "path";
3910
4085
  function shotMimetype(path) {
3911
4086
  const dot = path.lastIndexOf(".");
3912
4087
  if (dot < 0) return null;
@@ -3932,7 +4107,7 @@ async function readShots(paths, opts) {
3932
4107
  }
3933
4108
  let bytes;
3934
4109
  try {
3935
- bytes = await read(isAbsolute3(path) ? path : join19(opts.worktree, path));
4110
+ bytes = await read(isAbsolute3(path) ? path : join20(opts.worktree, path));
3936
4111
  } catch (e) {
3937
4112
  skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
3938
4113
  continue;
@@ -4010,15 +4185,15 @@ var init_shotUpload = __esm({
4010
4185
 
4011
4186
  // src/oauth.ts
4012
4187
  import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
4013
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3 } from "fs";
4014
- import { join as join20 } from "path";
4188
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
4189
+ import { join as join21 } from "path";
4015
4190
  function makePkce() {
4016
4191
  const verifier = randomBytes2(48).toString("base64url");
4017
4192
  const challenge = createHash2("sha256").update(verifier).digest("base64url");
4018
4193
  return { verifier, challenge };
4019
4194
  }
4020
4195
  async function registerClient(args, fetchFn = fetch) {
4021
- const res = await fetchFn(`${base(args.api)}${REGISTER}`, {
4196
+ const res = await fetchFn(`${base2(args.api)}${REGISTER}`, {
4022
4197
  method: "POST",
4023
4198
  headers: { "Content-Type": "application/json" },
4024
4199
  body: JSON.stringify({
@@ -4033,7 +4208,7 @@ async function registerClient(args, fetchFn = fetch) {
4033
4208
  return j.client_id;
4034
4209
  }
4035
4210
  function authorizeUrl(args) {
4036
- const u = new URL(`${base(args.api)}${AUTHORIZE}`);
4211
+ const u = new URL(`${base2(args.api)}${AUTHORIZE}`);
4037
4212
  u.searchParams.set("response_type", "code");
4038
4213
  u.searchParams.set("client_id", args.clientId);
4039
4214
  u.searchParams.set("redirect_uri", args.redirectUri);
@@ -4044,7 +4219,7 @@ function authorizeUrl(args) {
4044
4219
  return u.toString();
4045
4220
  }
4046
4221
  async function postToken(api, params, fetchFn) {
4047
- const res = await fetchFn(`${base(api)}${TOKEN}`, {
4222
+ const res = await fetchFn(`${base2(api)}${TOKEN}`, {
4048
4223
  method: "POST",
4049
4224
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
4050
4225
  body: new URLSearchParams(params).toString()
@@ -4095,23 +4270,23 @@ function toStore(clientId, t, now) {
4095
4270
  }
4096
4271
  function loadStore() {
4097
4272
  try {
4098
- return JSON.parse(readFileSync14(STORE_PATH, "utf8"));
4273
+ return JSON.parse(readFileSync15(STORE_PATH2, "utf8"));
4099
4274
  } catch {
4100
4275
  return null;
4101
4276
  }
4102
4277
  }
4103
4278
  function saveStore(s) {
4104
- writeFileSync10(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
4279
+ writeFileSync11(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
4105
4280
  }
4106
4281
  function clearStore() {
4107
4282
  try {
4108
- unlinkSync3(STORE_PATH);
4283
+ unlinkSync4(STORE_PATH2);
4109
4284
  } catch {
4110
4285
  }
4111
4286
  }
4112
4287
  function loadClientId() {
4113
4288
  try {
4114
- return JSON.parse(readFileSync14(CLIENT_PATH, "utf8")).clientId;
4289
+ return JSON.parse(readFileSync15(CLIENT_PATH, "utf8")).clientId;
4115
4290
  } catch {
4116
4291
  return null;
4117
4292
  }
@@ -4120,7 +4295,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
4120
4295
  const existing = loadClientId();
4121
4296
  if (existing) return existing;
4122
4297
  const clientId = await registerClient({ api, redirectUri }, fetchFn);
4123
- writeFileSync10(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
4298
+ writeFileSync11(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
4124
4299
  return clientId;
4125
4300
  }
4126
4301
  async function ensureAccess(store, opts = {}) {
@@ -4137,7 +4312,7 @@ async function ensureAccess(store, opts = {}) {
4137
4312
  return null;
4138
4313
  }
4139
4314
  }
4140
- var REGISTER, AUTHORIZE, TOKEN, SCOPE, STORE_PATH, CLIENT_PATH, base;
4315
+ var REGISTER, AUTHORIZE, TOKEN, SCOPE, STORE_PATH2, CLIENT_PATH, base2;
4141
4316
  var init_oauth = __esm({
4142
4317
  "src/oauth.ts"() {
4143
4318
  "use strict";
@@ -4146,64 +4321,45 @@ var init_oauth = __esm({
4146
4321
  AUTHORIZE = "/api/oauth/authorize";
4147
4322
  TOKEN = "/api/oauth/token";
4148
4323
  SCOPE = "openid profile email taskless:full";
4149
- STORE_PATH = join20(SWARM_HOME, "oauth.json");
4150
- CLIENT_PATH = join20(SWARM_HOME, "oauth-client.json");
4151
- base = (api) => api.replace(/\/$/, "");
4324
+ STORE_PATH2 = join21(SWARM_HOME, "oauth.json");
4325
+ CLIENT_PATH = join21(SWARM_HOME, "oauth-client.json");
4326
+ base2 = (api) => api.replace(/\/$/, "");
4152
4327
  }
4153
4328
  });
4154
4329
 
4155
- // src/agentToken.ts
4156
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
4157
- import { join as join21 } from "path";
4158
- function loadAgentToken() {
4159
- try {
4160
- const s = JSON.parse(readFileSync15(STORE_PATH2, "utf8"));
4161
- return s.token ? s : null;
4162
- } catch {
4163
- return null;
4164
- }
4165
- }
4166
- async function probeAgentToken(args, fetchFn = fetch) {
4167
- try {
4168
- const res = await fetchFn(`${base2(args.api)}/api/auth/me`, {
4169
- headers: { Authorization: `Bearer ${args.token}` }
4170
- });
4171
- if (res.status === 401 || res.status === 403) return "revoked";
4172
- return res.ok ? "ok" : "unreachable";
4173
- } catch {
4174
- return "unreachable";
4175
- }
4330
+ // src/authState.ts
4331
+ function noteTokenRejected(nowMs = Date.now()) {
4332
+ if (!denial) {
4333
+ denial = { since: nowMs, lastShoutAt: nowMs, count: 1 };
4334
+ return revokedTokenComplaint();
4335
+ }
4336
+ denial.count += 1;
4337
+ if (nowMs - denial.lastShoutAt < SHOUT_EVERY_MS) return null;
4338
+ denial.lastShoutAt = nowMs;
4339
+ return `${revokedTokenComplaint()}
4340
+ ${deniedSinceLine(nowMs)}
4341
+ `;
4176
4342
  }
4177
- function isRevokedTokenError(e) {
4178
- if (e && typeof e === "object" && e.code === 401) return true;
4179
- const text = e instanceof Error ? e.message : typeof e === "string" ? e : "";
4180
- return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
4343
+ function noteTokenAccepted() {
4344
+ denial = null;
4181
4345
  }
4182
- function revokedTokenLine() {
4183
- return "[taskless] \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (401 invalid_token) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443. \u041F\u043E\u0447\u0438\u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: swarm login";
4346
+ function authStateLine(nowMs = Date.now()) {
4347
+ if (!denial) return null;
4348
+ return `\u0414\u041E\u0421\u0422\u0423\u041F \u041A TASKLESS \u041E\u0422\u041E\u0417\u0412\u0410\u041D \u2014 ${deniedSinceLine(nowMs)}. \u041F\u043E\u0447\u0438\u043D\u0438 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: swarm login`;
4184
4349
  }
4185
- function revokedTokenComplaint() {
4186
- return [
4187
- "",
4188
- "\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
4189
- "\u2502 \u0422\u041E\u041A\u0415\u041D \u0420\u041E\u042F \u041D\u0415 \u041F\u0420\u0418\u041D\u042F\u0422 (401). \u0414\u043E\u0441\u0442\u0443\u043F \u043A Taskless \u0440\u0430\u0437\u043E\u0440\u0432\u0430\u043D. \u2502",
4190
- "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
4191
- "\u041F\u0440\u0438\u0447\u0438\u043D\u0430 \u2014 \u043E\u0434\u043D\u0430 \u0438\u0437 \u0434\u0432\u0443\u0445, \u043E\u0431\u0435 \u0434\u0435\u043B\u0430\u044E\u0442\u0441\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435:",
4192
- " \u2022 \xAB\u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F\xBB \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 (\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439) \u2014 \u0442\u043E\u043A\u0435\u043D \u043E\u0442\u043E\u0437\u0432\u0430\u043D;",
4193
- " \u2022 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u043C\u0430\u0441\u0442\u0435\u0440-\u0442\u0443\u043C\u0431\u043B\u0435\u0440\u043E\u043C \u2014 \u0442\u043E\u043A\u0435\u043D \u0430\u0433\u0435\u043D\u0442\u0430 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u043E\u0432\u0430\u0442\u044C.",
4194
- "",
4195
- "\u0414\u0435\u043C\u043E\u043D \u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E: \u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442, \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043D\u0435 \u0431\u0435\u0440\u0443\u0442\u0441\u044F \u0438 \u043E\u0442\u0447\u0451\u0442\u044B",
4196
- "\u043D\u0435 \u043F\u0438\u0448\u0443\u0442\u0441\u044F. \u041F\u043E\u0447\u0438\u043D\u0438 \u0432\u0445\u043E\u0434\u043E\u043C \u0437\u0430\u043D\u043E\u0432\u043E: swarm login",
4197
- ""
4198
- ].join("\n");
4350
+ function deniedSinceLine(nowMs) {
4351
+ const d = denial;
4352
+ if (!d) return "";
4353
+ const min = Math.max(1, Math.round((nowMs - d.since) / 6e4));
4354
+ return `\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0440\u0433\u0430\u0435\u0442 \u0442\u043E\u043A\u0435\u043D ${min} \u043C\u0438\u043D, \u043E\u0442\u043A\u0430\u0437\u043E\u0432 ${d.count}`;
4199
4355
  }
4200
- var STORE_PATH2, base2;
4201
- var init_agentToken = __esm({
4202
- "src/agentToken.ts"() {
4356
+ var SHOUT_EVERY_MS, denial;
4357
+ var init_authState = __esm({
4358
+ "src/authState.ts"() {
4203
4359
  "use strict";
4204
- init_config();
4205
- STORE_PATH2 = join21(SWARM_HOME, "agent.json");
4206
- base2 = (api) => api.replace(/\/$/, "");
4360
+ init_agentToken();
4361
+ SHOUT_EVERY_MS = 10 * 6e4;
4362
+ denial = null;
4207
4363
  }
4208
4364
  });
4209
4365
 
@@ -4244,6 +4400,7 @@ __export(taskless_exports, {
4244
4400
  extractText: () => extractText,
4245
4401
  fetchTaskContext: () => fetchTaskContext,
4246
4402
  isClosingTaskCategory: () => isClosingTaskCategory,
4403
+ lastSwarmConnectFailure: () => lastSwarmConnectFailure,
4247
4404
  makeFileFetcher: () => makeFileFetcher,
4248
4405
  makeTasklessClient: () => makeTasklessClient,
4249
4406
  parseWorkspaces: () => parseWorkspaces,
@@ -4588,12 +4745,14 @@ async function preflightAuth(opts = {}) {
4588
4745
  if (!agent?.token) return true;
4589
4746
  const probe = await probeAgentToken({ api, token: agent.token }, opts.fetchFn ?? fetch);
4590
4747
  if (probe === "revoked") {
4591
- out(revokedTokenComplaint());
4748
+ out(noteTokenRejected() ?? revokedTokenComplaint());
4592
4749
  return false;
4593
4750
  }
4594
4751
  if (probe === "unreachable") {
4595
4752
  out("[taskless] \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0434 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D) \u2014 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u044E, \u043D\u043E \u0441\u0432\u044F\u0437\u0438 \u043C\u043E\u0436\u0435\u0442 \u043D\u0435 \u0431\u044B\u0442\u044C.");
4753
+ return true;
4596
4754
  }
4755
+ noteTokenAccepted();
4597
4756
  return true;
4598
4757
  }
4599
4758
  async function connectTaskless(cfg) {
@@ -4610,11 +4769,15 @@ async function connectTaskless(cfg) {
4610
4769
  try {
4611
4770
  await client.connect(transport, { timeout: TASKLESS_CONNECT_TIMEOUT_MS });
4612
4771
  } catch (e) {
4613
- console.warn(
4614
- isRevokedTokenError(e) ? `${revokedTokenLine()} (\u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442 \u2014 slug-only \u0440\u0435\u0436\u0438\u043C)` : `Taskless: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${e instanceof Error ? e.message : e}) \u2192 slug-only. \u0412\u043E\u0439\u0434\u0438 \u0437\u0430\u043D\u043E\u0432\u043E.`
4615
- );
4772
+ if (isRevokedTokenError(e)) {
4773
+ const shout = noteTokenRejected();
4774
+ if (shout) console.error(shout);
4775
+ } else {
4776
+ console.warn(`Taskless: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${describeError(e)}) \u2192 slug-only. \u0412\u043E\u0439\u0434\u0438 \u0437\u0430\u043D\u043E\u0432\u043E.`);
4777
+ }
4616
4778
  return null;
4617
4779
  }
4780
+ noteTokenAccepted();
4618
4781
  const call = async (name, args) => await client.callTool({ name, arguments: args });
4619
4782
  return makeTasklessClient(
4620
4783
  call,
@@ -4627,27 +4790,41 @@ async function connectSwarmMcp(cfg) {
4627
4790
  const api = cfg?.api ?? TASKLESS_API;
4628
4791
  const workspace = cfg?.workspace ?? resolvedWorkspaceId();
4629
4792
  const token = await resolveApiToken(api, cfg?.token);
4630
- if (!token) return null;
4793
+ if (!token) {
4794
+ swarmConnectFailure = "\u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0442 \u0442\u043E\u043A\u0435\u043D\u0430 \u0440\u043E\u044F \u2014 \u043D\u0443\u0436\u0435\u043D \u0432\u0445\u043E\u0434: swarm login";
4795
+ return null;
4796
+ }
4631
4797
  const url = new URL(mcpEndpoint(api));
4632
4798
  const transport = new StreamableHTTPClientTransport(url, {
4633
4799
  requestInit: { headers: { Authorization: `Bearer ${token}` } }
4634
4800
  });
4635
4801
  const client = new Client({ name: "taskless-swarm-client", version: "0.1.0" }, { capabilities: {} });
4636
4802
  try {
4637
- await client.connect(transport);
4803
+ await client.connect(transport, { timeout: TASKLESS_CONNECT_TIMEOUT_MS });
4638
4804
  } catch (e) {
4639
- console.warn(
4640
- isRevokedTokenError(e) ? `${revokedTokenLine()} (\u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442 \u2014 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox)` : `Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${e instanceof Error ? e.message : e}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`
4641
- );
4805
+ if (isRevokedTokenError(e)) {
4806
+ swarmConnectFailure = revokedTokenLine();
4807
+ const shout = noteTokenRejected();
4808
+ if (shout) console.error(`${shout}
4809
+ \u0421\u043E\u0431\u044B\u0442\u0438\u044F \u043F\u043E\u043A\u0430 \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox \u0438 \u0443\u0435\u0434\u0443\u0442, \u043A\u043E\u0433\u0434\u0430 \u0434\u043E\u0441\u0442\u0443\u043F \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F.`);
4810
+ } else {
4811
+ swarmConnectFailure = describeError(e);
4812
+ console.warn(`Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${swarmConnectFailure}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`);
4813
+ }
4642
4814
  return null;
4643
4815
  }
4816
+ swarmConnectFailure = null;
4817
+ noteTokenAccepted();
4644
4818
  return async (name, args) => await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) });
4645
4819
  }
4820
+ function lastSwarmConnectFailure() {
4821
+ return swarmConnectFailure;
4822
+ }
4646
4823
  function swarmToolArgs(name, args, workspace) {
4647
4824
  if (!WS_SCOPED_SWARM_TOOLS.has(name) || !workspace) return args;
4648
4825
  return { workspaceId: workspace, ...args };
4649
4826
  }
4650
- var SLUG_RE, WS_FREE_TOOLS, TaskBodyUnavailableError, TASK_CONTEXT_MAX_DOCS, TASK_CONTEXT_MAX_DOC_CHARS, TASK_CONTEXT_MAX_COMMENTS, TASK_CONTEXT_MAX_COMMENT_CHARS, UNTRUSTED_TASK_INPUT_NOTICE, TASKLESS_CONNECT_TIMEOUT_MS, FILE_FETCH_TIMEOUT_MS, FILE_FETCH_MAX_REDIRECTS, legacyWarned, WS_SCOPED_SWARM_TOOLS;
4827
+ var SLUG_RE, WS_FREE_TOOLS, TaskBodyUnavailableError, TASK_CONTEXT_MAX_DOCS, TASK_CONTEXT_MAX_DOC_CHARS, TASK_CONTEXT_MAX_COMMENTS, TASK_CONTEXT_MAX_COMMENT_CHARS, UNTRUSTED_TASK_INPUT_NOTICE, TASKLESS_CONNECT_TIMEOUT_MS, FILE_FETCH_TIMEOUT_MS, FILE_FETCH_MAX_REDIRECTS, legacyWarned, swarmConnectFailure, WS_SCOPED_SWARM_TOOLS;
4651
4828
  var init_taskless = __esm({
4652
4829
  "src/taskless.ts"() {
4653
4830
  "use strict";
@@ -4657,6 +4834,8 @@ var init_taskless = __esm({
4657
4834
  init_config();
4658
4835
  init_oauth();
4659
4836
  init_agentToken();
4837
+ init_linkFailure();
4838
+ init_authState();
4660
4839
  init_workspace();
4661
4840
  SLUG_RE = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
4662
4841
  WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload", "report_acceptance_check"]);
@@ -4687,6 +4866,7 @@ var init_taskless = __esm({
4687
4866
  FILE_FETCH_TIMEOUT_MS = 15e3;
4688
4867
  FILE_FETCH_MAX_REDIRECTS = 3;
4689
4868
  legacyWarned = false;
4869
+ swarmConnectFailure = null;
4690
4870
  WS_SCOPED_SWARM_TOOLS = /* @__PURE__ */ new Set(["swarm_run_upsert", "get_task"]);
4691
4871
  }
4692
4872
  });
@@ -4748,6 +4928,10 @@ function rowToRun(row) {
4748
4928
  resolves_run_id: row.resolves_run_id ?? null,
4749
4929
  reviews_run_id: row.reviews_run_id ?? null,
4750
4930
  review_verdict: row.review_verdict ?? null,
4931
+ verifies_run_id: row.verifies_run_id ?? null,
4932
+ // TASWM-344: через узкий парсер, а не сырой строкой — в зеркале может лежать значение,
4933
+ // записанное демоном другой версии; непонятное здесь значит «вердикта нет».
4934
+ verify_verdict: toVerifyVerdict(row.verify_verdict),
4751
4935
  review_phase: row.review_phase ?? null,
4752
4936
  // вычисляемая фаза ревью (TASWM-230)
4753
4937
  review_cycle: row.review_cycle ?? 0,
@@ -4816,6 +5000,8 @@ CREATE TABLE IF NOT EXISTS runs (
4816
5000
  resolves_run_id INTEGER,
4817
5001
  reviews_run_id INTEGER,
4818
5002
  review_verdict TEXT,
5003
+ verifies_run_id INTEGER,
5004
+ verify_verdict TEXT,
4819
5005
  review_cycle INTEGER NOT NULL DEFAULT 0,
4820
5006
  task_title TEXT,
4821
5007
  task_desc TEXT,
@@ -4892,6 +5078,12 @@ CREATE TABLE IF NOT EXISTS imports (
4892
5078
  // вердикт (approve|changes), который ревьюер пишет на проверяемый прогон.
4893
5079
  "ALTER TABLE runs ADD COLUMN reviews_run_id INTEGER",
4894
5080
  "ALTER TABLE runs ADD COLUMN review_verdict TEXT",
5081
+ // Верификатор (TASWM-344): прогон, который пытается ОПРОВЕРГНУТЬ работу
5082
+ // #verifies_run_id, и его вердикт (confirmed|not_reproduced|found_issues) на
5083
+ // проверяемом прогоне. Своя пара колонок, а не переиспользование reviews_*:
5084
+ // ревью и верификация идут на одном PR одновременно и считаются раздельно.
5085
+ "ALTER TABLE runs ADD COLUMN verifies_run_id INTEGER",
5086
+ "ALTER TABLE runs ADD COLUMN verify_verdict TEXT",
4895
5087
  // Авто-ревью v2 (TASWM-162): номер цикла ревью — пере-ревью после фикс-итерации.
4896
5088
  "ALTER TABLE runs ADD COLUMN review_cycle INTEGER NOT NULL DEFAULT 0",
4897
5089
  // Вычисляемая фаза ревью (TASWM-230): человеко-фраза «чего ждёт PR», её считает демон.
@@ -4928,7 +5120,7 @@ CREATE TABLE IF NOT EXISTS imports (
4928
5120
  createRun(input) {
4929
5121
  const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
4930
5122
  const res = this.db.prepare(
4931
- "INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
5123
+ "INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, verifies_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
4932
5124
  ).run(
4933
5125
  input.task_slug,
4934
5126
  name,
@@ -4946,6 +5138,8 @@ CREATE TABLE IF NOT EXISTS imports (
4946
5138
  input.decision ? JSON.stringify(input.decision) : null,
4947
5139
  input.resolves_run_id ?? null,
4948
5140
  input.reviews_run_id ?? null,
5141
+ input.verifies_run_id ?? null,
5142
+ // прогон-верификатор (TASWM-344); обычный прогон — null
4949
5143
  input.review_cycle ?? 0,
4950
5144
  // цикл ревью для ревьюера (авто-ревью v2, TASWM-162); обычный прогон — 0
4951
5145
  input.priority ?? 0,
@@ -4956,7 +5150,7 @@ CREATE TABLE IF NOT EXISTS imports (
4956
5150
  // воркспейс задачи прогона (TASWM-288); null → глобальный пин
4957
5151
  );
4958
5152
  const id = Number(res.lastInsertRowid);
4959
- const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
5153
+ const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.verifies_run_id ? `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0434\u043B\u044F #${input.verifies_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
4960
5154
  this.addEvent(id, "spawned", spawnNote);
4961
5155
  return this.getRun(id);
4962
5156
  }
@@ -4982,13 +5176,14 @@ CREATE TABLE IF NOT EXISTS imports (
4982
5176
  */
4983
5177
  ensureRun(input) {
4984
5178
  this.db.prepare(
4985
- "INSERT OR IGNORE INTO runs (id, task_slug, kind, status, name, reviews_run_id, hero_run_id) VALUES (?, ?, ?, 'running', ?, ?, ?)"
5179
+ "INSERT OR IGNORE INTO runs (id, task_slug, kind, status, name, reviews_run_id, verifies_run_id, hero_run_id) VALUES (?, ?, ?, 'running', ?, ?, ?, ?)"
4986
5180
  ).run(
4987
5181
  input.id,
4988
5182
  input.task_slug,
4989
5183
  input.kind ?? "drone",
4990
5184
  input.name || "(\u0438\u043C\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E)",
4991
5185
  input.reviews_run_id ?? null,
5186
+ input.verifies_run_id ?? null,
4992
5187
  input.hero_run_id ?? null
4993
5188
  );
4994
5189
  }
@@ -5133,6 +5328,31 @@ CREATE TABLE IF NOT EXISTS imports (
5133
5328
  const rows = this.db.prepare(`SELECT * FROM runs WHERE reviews_run_id = ? AND status NOT IN (${TERMINAL_SQL}) ORDER BY id DESC`).all(reviewRunId);
5134
5329
  return rows.map(rowToRun);
5135
5330
  }
5331
+ /**
5332
+ * ВЕРИФИКАЦИЯ (TASWM-344) — те же три счётчика, что у ревью выше, но по своей связи
5333
+ * `verifies_run_id` и со своим капом. Скоуп «текущий цикл ревью» тот же и по той же
5334
+ * причине: фикс-итерация бампит review_cycle и обнуляет вердикты (reviewTransitionPatch),
5335
+ * значит исправленную работу обязан проверить СВЕЖИЙ верификатор, а не считаться уже
5336
+ * полученным подтверждением по коду, которого больше нет.
5337
+ */
5338
+ verifierCountFor(verifyRunId) {
5339
+ const row = this.db.prepare(
5340
+ "SELECT COUNT(*) n FROM runs WHERE verifies_run_id = ? AND status NOT IN ('failed','stalled','stopped','superseded') AND review_cycle = COALESCE((SELECT review_cycle FROM runs WHERE id = ?), 0)"
5341
+ ).get(verifyRunId, verifyRunId);
5342
+ return row.n;
5343
+ }
5344
+ /** Плоский кап попыток верификации на цикл — любой статус, кроме сброшенных оператором. */
5345
+ verifierAttemptCountFor(verifyRunId) {
5346
+ const row = this.db.prepare(
5347
+ "SELECT COUNT(*) n FROM runs WHERE verifies_run_id = ? AND status != 'superseded' AND review_cycle = COALESCE((SELECT review_cycle FROM runs WHERE id = ?), 0)"
5348
+ ).get(verifyRunId, verifyRunId);
5349
+ return row.n;
5350
+ }
5351
+ /** Нетерминальные верификаторы прогона — закрыть зависших, когда PR уже смержен/закрыт. */
5352
+ activeVerifiersFor(verifyRunId) {
5353
+ const rows = this.db.prepare(`SELECT * FROM runs WHERE verifies_run_id = ? AND status NOT IN (${TERMINAL_SQL}) ORDER BY id DESC`).all(verifyRunId);
5354
+ return rows.map(rowToRun);
5355
+ }
5136
5356
  /**
5137
5357
  * Последний живой (не терминальный) прогон по slug — для swarm cancel. Служебные
5138
5358
  * сабраны (reviews_run_id/resolves_run_id — ревьюер/резолвер, TASWM-273) исключены:
@@ -5147,7 +5367,7 @@ CREATE TABLE IF NOT EXISTS imports (
5147
5367
  */
5148
5368
  activeRunForSlug(slug) {
5149
5369
  const row = this.db.prepare(
5150
- `SELECT * FROM runs WHERE task_slug = ? AND status NOT IN (${TERMINAL_SQL}) AND reviews_run_id IS NULL AND resolves_run_id IS NULL ORDER BY id DESC LIMIT 1`
5370
+ `SELECT * FROM runs WHERE task_slug = ? AND status NOT IN (${TERMINAL_SQL}) AND reviews_run_id IS NULL AND resolves_run_id IS NULL AND verifies_run_id IS NULL ORDER BY id DESC LIMIT 1`
5151
5371
  ).get(slug);
5152
5372
  return row ? rowToRun(row) : null;
5153
5373
  }
@@ -5187,7 +5407,7 @@ CREATE TABLE IF NOT EXISTS imports (
5187
5407
  if (!depends_on || depends_on.length === 0) return [];
5188
5408
  const slugs = [...new Set(depends_on)];
5189
5409
  const rows = this.db.prepare(
5190
- `SELECT task_slug, status, name, stop_reason FROM runs WHERE task_slug IN (${slugs.map(() => "?").join(",")}) AND resolves_run_id IS NULL AND reviews_run_id IS NULL ORDER BY id`
5410
+ `SELECT task_slug, status, name, stop_reason FROM runs WHERE task_slug IN (${slugs.map(() => "?").join(",")}) AND resolves_run_id IS NULL AND reviews_run_id IS NULL AND verifies_run_id IS NULL ORDER BY id`
5191
5411
  ).all(...slugs);
5192
5412
  const bySlug = /* @__PURE__ */ new Map();
5193
5413
  for (const r of rows) {
@@ -5935,7 +6155,16 @@ var SwarmClient = class _SwarmClient {
5935
6155
  static REJECT_LIMIT = 5;
5936
6156
  static BACKOFF_BASE = 500;
5937
6157
  static BACKOFF_MAX = 3e4;
6158
+ // TASWM-325: момент УДАЧНОГО пульса — «когда прод последний раз слышал эту машину»
6159
+ // (прод пишет SwarmHost.lastSeenAt ровно на swarm_heartbeat). Единственное, что про
6160
+ // связь знает КЛИЕНТ: жива ли она вообще — знает транспорт (mcpResilient.link), а
6161
+ // отвергнут ли токен — authState. Здесь их не угадываем: первая редакция считала
6162
+ // успехом любой возврат call и на боевом каллере (который не бросает, а возвращает
6163
+ // isError) всегда показывала «связь есть».
6164
+ heartbeatOkAt = null;
6165
+ now;
5938
6166
  constructor(opts) {
6167
+ this.now = opts.now ?? Date.now;
5939
6168
  this.call = opts.call;
5940
6169
  this.defaultRepo = normalizeRepo(opts.defaultRepo ?? "");
5941
6170
  this.autoFlush = opts.autoFlush ?? true;
@@ -6078,6 +6307,7 @@ var SwarmClient = class _SwarmClient {
6078
6307
  if ("decision" in patch) out.decision = patch.decision ? JSON.stringify(patch.decision) : null;
6079
6308
  if ("kind" in patch && patch.kind !== void 0) out.role = patch.kind;
6080
6309
  if ("review_verdict" in patch) out.reviewVerdict = patch.review_verdict ?? null;
6310
+ if ("verify_verdict" in patch) out.verifyVerdict = patch.verify_verdict ?? null;
6081
6311
  if ("host_id" in patch && patch.host_id) {
6082
6312
  out.hostId = patch.host_id;
6083
6313
  out.hostPinned = patch.host_pinned === true;
@@ -6088,6 +6318,7 @@ var SwarmClient = class _SwarmClient {
6088
6318
  this.setRunRefField(out, "resolvesRunId", patch, "resolves_run_id");
6089
6319
  this.setRunRefField(out, "supersededById", patch, "superseded_by");
6090
6320
  this.setRunRefField(out, "reviewsRunId", patch, "reviews_run_id");
6321
+ this.setRunRefField(out, "verifiesRunId", patch, "verifies_run_id");
6091
6322
  return out;
6092
6323
  }
6093
6324
  // Положить self-ref прогона в прод-патч: локальный number → отложенная ссылка
@@ -6150,6 +6381,9 @@ var SwarmClient = class _SwarmClient {
6150
6381
  resolves_run_id: input.resolves_run_id ?? null,
6151
6382
  reviews_run_id: input.reviews_run_id ?? null,
6152
6383
  review_verdict: null,
6384
+ verifies_run_id: input.verifies_run_id ?? null,
6385
+ // прогон-верификатор (TASWM-344)
6386
+ verify_verdict: null,
6153
6387
  review_phase: null,
6154
6388
  // вычисляемая фаза ревью (TASWM-230) — демон посчитает на review-тике
6155
6389
  review_cycle: input.review_cycle ?? 0,
@@ -6201,6 +6435,8 @@ var SwarmClient = class _SwarmClient {
6201
6435
  // резолвер конфликта → связь на review-прогон
6202
6436
  reviews_run_id: d.reviews_run_id,
6203
6437
  // ревьюер (авто-ревью, TASWM-148) → связь на review-прогон
6438
+ verifies_run_id: d.verifies_run_id,
6439
+ // верификатор (TASWM-344) → связь на проверяемый прогон
6204
6440
  host_id: d.host_id,
6205
6441
  // машина прогона (TASWM-281) — своя с рождения либо заданная --host
6206
6442
  host_pinned: d.host_pinned,
@@ -6208,7 +6444,7 @@ var SwarmClient = class _SwarmClient {
6208
6444
  base_branch: d.base_branch
6209
6445
  // базовая ветка PR (TASWM-285), если задана на спавне
6210
6446
  }));
6211
- const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
6447
+ const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.verifies_run_id ? `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0434\u043B\u044F #${input.verifies_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
6212
6448
  this.addEvent(id, "spawned", spawnNote);
6213
6449
  return this.mat(d);
6214
6450
  }
@@ -6312,6 +6548,24 @@ var SwarmClient = class _SwarmClient {
6312
6548
  activeReviewersFor(reviewRunId) {
6313
6549
  return [...this.runs.values()].filter((r) => r.reviews_run_id === reviewRunId && !TERMINAL_STATUSES.includes(r.status)).sort((a, b) => b.id - a.id).map((d) => this.mat(d));
6314
6550
  }
6551
+ // Верификация (TASWM-344) — зеркало трёх счётчиков ревью выше по своей связи
6552
+ // verifies_run_id; правила «кто не в счёт» те же (см. db.ts:verifierCountFor).
6553
+ verifierCountFor(verifyRunId) {
6554
+ const cycle = this.runs.get(verifyRunId)?.review_cycle ?? 0;
6555
+ const dead = /* @__PURE__ */ new Set(["failed", "stalled", "stopped", "superseded"]);
6556
+ return [...this.runs.values()].filter(
6557
+ (r) => r.verifies_run_id === verifyRunId && r.review_cycle === cycle && !dead.has(r.status)
6558
+ ).length;
6559
+ }
6560
+ verifierAttemptCountFor(verifyRunId) {
6561
+ const cycle = this.runs.get(verifyRunId)?.review_cycle ?? 0;
6562
+ return [...this.runs.values()].filter(
6563
+ (r) => r.verifies_run_id === verifyRunId && r.review_cycle === cycle && r.status !== "superseded"
6564
+ ).length;
6565
+ }
6566
+ activeVerifiersFor(verifyRunId) {
6567
+ return [...this.runs.values()].filter((r) => r.verifies_run_id === verifyRunId && !TERMINAL_STATUSES.includes(r.status)).sort((a, b) => b.id - a.id).map((d) => this.mat(d));
6568
+ }
6315
6569
  activeRunForSlug(slug) {
6316
6570
  const cands = [...this.runs.values()].filter((r) => r.task_slug === slug && !SLUG_TERMINAL.has(r.status)).sort((a, b) => b.id - a.id);
6317
6571
  return cands.length ? this.mat(cands[0]) : null;
@@ -6347,7 +6601,9 @@ var SwarmClient = class _SwarmClient {
6347
6601
  if (!depends_on || depends_on.length === 0) return [];
6348
6602
  const doneInMirror = /* @__PURE__ */ new Set();
6349
6603
  for (const r of this.runs.values()) {
6350
- if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null) doneInMirror.add(r.task_slug);
6604
+ if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null && r.verifies_run_id == null) {
6605
+ doneInMirror.add(r.task_slug);
6606
+ }
6351
6607
  }
6352
6608
  return depends_on.map((slug) => {
6353
6609
  if (doneInMirror.has(slug)) {
@@ -6393,7 +6649,7 @@ var SwarmClient = class _SwarmClient {
6393
6649
  // Служебный сабран под тем же слагом (резолвер конфликта / ревьюер): его финал —
6394
6650
  // не финал задачи. Резолвер вычёркивался и до TASWM-361, ревьюер добавлен вместе
6395
6651
  // с расширением критерия — иначе остановленный ревьюер закрывал бы зависимость.
6396
- service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId),
6652
+ service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId) || Boolean(r.verifiesRunId),
6397
6653
  // В проде имя дрона — колонка droneName (см. hydrateRun); `name` терпим как
6398
6654
  // запасной вариант ради фикстур и старых ответов.
6399
6655
  name: r.droneName ?? r.name ?? null,
@@ -6609,7 +6865,7 @@ var SwarmClient = class _SwarmClient {
6609
6865
  return this.eventsLog.filter((e) => {
6610
6866
  if (e.kind !== "failed" || e.run_id == null || e.at < cutoff) return false;
6611
6867
  const r = this.runs.get(e.run_id);
6612
- return !!r && r.resolves_run_id == null && r.reviews_run_id == null;
6868
+ return !!r && r.resolves_run_id == null && r.reviews_run_id == null && r.verifies_run_id == null;
6613
6869
  }).length;
6614
6870
  }
6615
6871
  recordStormProbe(probe, ext, reason, resetHint) {
@@ -6734,7 +6990,7 @@ var SwarmClient = class _SwarmClient {
6734
6990
  const workerWt = droneWorktreePath(opts.worktreesDir, d.repo || this.defaultRepo, d.task_slug);
6735
6991
  let matchWt = null;
6736
6992
  let matchPid = null;
6737
- if (d.resolves_run_id == null && d.reviews_run_id == null) {
6993
+ if (d.resolves_run_id == null && d.reviews_run_id == null && d.verifies_run_id == null) {
6738
6994
  const p = pidByWorktree.get(workerWt);
6739
6995
  if (p != null) {
6740
6996
  matchWt = workerWt;
@@ -6750,9 +7006,10 @@ var SwarmClient = class _SwarmClient {
6750
7006
  }
6751
7007
  }
6752
7008
  } else {
6753
- const reviewed = this.runs.get(d.reviews_run_id);
7009
+ const checkedId = d.reviews_run_id ?? d.verifies_run_id;
7010
+ const reviewed = this.runs.get(checkedId);
6754
7011
  const reviewedWt = reviewed ? droneWorktreePath(opts.worktreesDir, reviewed.repo || this.defaultRepo, reviewed.task_slug) : workerWt;
6755
- const prefix = `${reviewedWt}-review-`;
7012
+ const prefix = d.reviews_run_id != null ? `${reviewedWt}-review-` : `${reviewedWt}-verify-`;
6756
7013
  for (const [wt, p] of pidByWorktree) {
6757
7014
  if (wt.startsWith(prefix)) {
6758
7015
  matchWt = wt;
@@ -6765,7 +7022,7 @@ var SwarmClient = class _SwarmClient {
6765
7022
  this.runtime.set(d.id, { pid: matchPid, worktree_path: matchWt });
6766
7023
  alive++;
6767
7024
  } else {
6768
- const wt = d.resolves_run_id == null && d.reviews_run_id == null && wtExists(workerWt) ? workerWt : null;
7025
+ const wt = d.resolves_run_id == null && d.reviews_run_id == null && d.verifies_run_id == null && wtExists(workerWt) ? workerWt : null;
6769
7026
  this.runtime.set(d.id, { pid: null, worktree_path: wt });
6770
7027
  dead++;
6771
7028
  }
@@ -6786,6 +7043,8 @@ var SwarmClient = class _SwarmClient {
6786
7043
  if (sup) local.superseded_by = this.localIdForProdId(sup) ?? local.superseded_by;
6787
7044
  const rev = pr.reviewsRunId ?? null;
6788
7045
  if (rev) local.reviews_run_id = this.localIdForProdId(rev) ?? local.reviews_run_id;
7046
+ const ver = pr.verifiesRunId ?? null;
7047
+ if (ver) local.verifies_run_id = this.localIdForProdId(ver) ?? local.verifies_run_id;
6789
7048
  }
6790
7049
  /**
6791
7050
  * Авто-интейк (TASWM-100): взять следующую claimable-задачу владельца токена через
@@ -7282,9 +7541,16 @@ var SwarmClient = class _SwarmClient {
7282
7541
  * простоя, видимого только в логе на самой машине. Не передали (нечем измерить) → ключ
7283
7542
  * не шлём, прод прошлое не трогает — та же логика, что у capacity/repoFullNames.
7284
7543
  *
7544
+ * TASWM-333: тем же пульсом уходит ШТОРМ этой машины (`storm` — штормим ли сейчас, и с
7545
+ * какой формулировкой мы в него вошли). Состояние живёт в наших событиях и переживает
7546
+ * рестарт, а в проде его не было вовсе: рой стоял, а парк машин показывал живую свободную
7547
+ * машину — 10.08 это стоило десяти часов простоя, замеченных человеком вручную. Шлём
7548
+ * ВСЕГДА (в т.ч. `storm:false`), как паузу: это состояние, а не команда, и молчание тут
7549
+ * означало бы «прод помнит вчерашний шторм», когда рой давно работает.
7550
+ *
7285
7551
  * Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
7286
7552
  */
7287
- async heartbeat(daemonVersion, pause, capacity, resources) {
7553
+ async heartbeat(daemonVersion, pause, capacity, resources, storm) {
7288
7554
  try {
7289
7555
  const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
7290
7556
  if (daemonVersion) args.daemonVersion = daemonVersion;
@@ -7294,12 +7560,17 @@ var SwarmClient = class _SwarmClient {
7294
7560
  args.paused = !!pause?.paused;
7295
7561
  if (pause?.reason) args.pauseReason = pause.reason;
7296
7562
  }
7563
+ if (storm !== void 0 && storm !== null) {
7564
+ args.storm = storm.active;
7565
+ if (storm.reason) args.stormReason = storm.reason;
7566
+ }
7297
7567
  if (this.host) {
7298
7568
  args.hostId = this.host.hostId;
7299
7569
  args.hostName = this.host.hostName;
7300
7570
  }
7301
7571
  const res = await this.call("swarm_heartbeat", args);
7302
7572
  if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
7573
+ this.heartbeatOkAt = this.now();
7303
7574
  const data = res?.structuredContent ?? {};
7304
7575
  return {
7305
7576
  commands: Array.isArray(data.commands) ? data.commands.filter(isHostCommandShape) : [],
@@ -7511,6 +7782,9 @@ var SwarmClient = class _SwarmClient {
7511
7782
  resolves_run_id: null,
7512
7783
  reviews_run_id: null,
7513
7784
  review_verdict: null,
7785
+ verifies_run_id: null,
7786
+ // прогон-верификатор (TASWM-344) — прод-поле, заполняется ниже
7787
+ verify_verdict: null,
7514
7788
  review_phase: null,
7515
7789
  // вычисляемая фаза ревью (TASWM-230) — прод хранит и раздаёт
7516
7790
  // Локальная бухгалтерия (в прод-схему не уходит) — на гидрации из прода дефолт 0;
@@ -7554,6 +7828,9 @@ var SwarmClient = class _SwarmClient {
7554
7828
  const revProd = pr.reviewsRunId ?? null;
7555
7829
  if (revProd) local.reviews_run_id = this.localIdForProdId(revProd) ?? local.reviews_run_id;
7556
7830
  local.review_verdict = pr.reviewVerdict ?? local.review_verdict;
7831
+ const verProd = pr.verifiesRunId ?? null;
7832
+ if (verProd) local.verifies_run_id = this.localIdForProdId(verProd) ?? local.verifies_run_id;
7833
+ local.verify_verdict = toVerifyVerdict(pr.verifyVerdict) ?? local.verify_verdict;
7557
7834
  local.review_phase = pr.reviewPhase ?? local.review_phase;
7558
7835
  local.repo = this.resolveRepo(pr.repo ?? null);
7559
7836
  local.host_id = pr.hostId ?? null;
@@ -7842,6 +8119,18 @@ var SwarmClient = class _SwarmClient {
7842
8119
  pendingCount() {
7843
8120
  return this.pending.length;
7844
8121
  }
8122
+ /**
8123
+ * Когда прод в последний раз ПРИНЯЛ пульс этой машины (ISO) — вход `swarm status`.
8124
+ * null — ни разу за жизнь процесса.
8125
+ *
8126
+ * Почему именно пульс, а не «последний удачный вызов»: в парке машин lastSeenAt прод
8127
+ * пишет ровно на swarm_heartbeat. Спрашивать за тем же самым swarm_list_hosts значило бы
8128
+ * завести второй источник правды и платить прод-вызовом в команде, которую зовут именно
8129
+ * тогда, когда прод не отвечает.
8130
+ */
8131
+ lastHeardAt() {
8132
+ return this.heartbeatOkAt == null ? null : new Date(this.heartbeatOkAt).toISOString();
8133
+ }
7845
8134
  close() {
7846
8135
  if (this.flushTimer) {
7847
8136
  clearTimeout(this.flushTimer);
@@ -7995,13 +8284,14 @@ function pickRepoPaths(args) {
7995
8284
  // src/repoDiscovery.ts
7996
8285
  init_src();
7997
8286
  init_config();
8287
+ init_linkFailure();
7998
8288
  import { homedir as homedir3 } from "os";
7999
- import { readFileSync as readFileSync8, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
8000
- import { isAbsolute, join as join9, resolve as resolve2 } from "path";
8289
+ import { readFileSync as readFileSync9, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "fs";
8290
+ import { isAbsolute, join as join10, resolve as resolve2 } from "path";
8001
8291
 
8002
8292
  // src/repoScan.ts
8003
- import { readdirSync as readdirSync5, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
8004
- import { join as join8 } from "path";
8293
+ import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
8294
+ import { join as join9 } from "path";
8005
8295
  var SKIP = /* @__PURE__ */ new Set([
8006
8296
  "node_modules",
8007
8297
  "vendor",
@@ -8031,7 +8321,7 @@ function parseOriginFromGitConfig(text) {
8031
8321
  }
8032
8322
  function originOf(dir) {
8033
8323
  try {
8034
- return parseOriginFromGitConfig(readFileSync7(join8(dir, ".git", "config"), "utf8"));
8324
+ return parseOriginFromGitConfig(readFileSync8(join9(dir, ".git", "config"), "utf8"));
8035
8325
  } catch {
8036
8326
  return null;
8037
8327
  }
@@ -8066,7 +8356,7 @@ function walk(dir, depth, maxDepth, found) {
8066
8356
  }
8067
8357
  for (const name of entries) {
8068
8358
  if (name.startsWith(".") || SKIP.has(name)) continue;
8069
- const child = join8(dir, name);
8359
+ const child = join9(dir, name);
8070
8360
  try {
8071
8361
  if (!statSync5(child).isDirectory()) continue;
8072
8362
  } catch {
@@ -8088,7 +8378,7 @@ function defaultScanRoots(home, knownPaths = []) {
8088
8378
  if (parent && parent !== p) add(parent);
8089
8379
  }
8090
8380
  for (const name of ["projects", "code", "src", "dev", "work", "repos", "git", "Projects", "Code", "Developer", "Documents/GitHub", "go/src"]) {
8091
- add(join8(home, name));
8381
+ add(join9(home, name));
8092
8382
  }
8093
8383
  return out;
8094
8384
  }
@@ -8102,7 +8392,7 @@ function resolveScanRoots(raw, home = homedir3(), knownPaths = []) {
8102
8392
  for (const part of v.split(",")) {
8103
8393
  const p = part.trim();
8104
8394
  if (!p) continue;
8105
- const abs = p === "~" ? home : p.startsWith("~/") ? join9(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
8395
+ const abs = p === "~" ? home : p.startsWith("~/") ? join10(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
8106
8396
  if (!out.includes(abs)) out.push(abs);
8107
8397
  }
8108
8398
  return out;
@@ -8115,16 +8405,24 @@ function normalizeFullNames(list) {
8115
8405
  }
8116
8406
  return out;
8117
8407
  }
8408
+ function errorText(res) {
8409
+ return (res.content ?? []).map((c) => c?.text ?? "").filter(Boolean).join(" ").trim();
8410
+ }
8118
8411
  async function fetchSwarmRepos(call) {
8119
8412
  let res;
8120
8413
  try {
8121
8414
  res = await call("swarm_my_repos", {});
8122
- } catch {
8123
- return null;
8415
+ } catch (e) {
8416
+ return { ok: false, ...explainLinkFailure(e) };
8417
+ }
8418
+ if (!res) return { ok: false, kind: "offline", detail: "\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u0434\u0430\u043B \u043E\u0442\u0432\u0435\u0442\u0430" };
8419
+ if (res.isError) {
8420
+ return { ok: false, ...explainLinkFailure(errorText(res) || "\u0431\u0435\u0437 \u0442\u0435\u043A\u0441\u0442\u0430 \u043E\u0448\u0438\u0431\u043A\u0438") };
8124
8421
  }
8125
- if (!res || res.isError) return null;
8126
8422
  const raw = res.structuredContent?.repos;
8127
- if (!Array.isArray(raw)) return null;
8423
+ if (!Array.isArray(raw)) {
8424
+ return { ok: false, kind: "malformed", detail: `\u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 \u0441\u043F\u0438\u0441\u043A\u0430 repos (${typeof raw})` };
8425
+ }
8128
8426
  const out = [];
8129
8427
  for (const item of raw) {
8130
8428
  if (!item || typeof item !== "object") continue;
@@ -8137,7 +8435,24 @@ async function fetchSwarmRepos(call) {
8137
8435
  workspaceSlug: typeof r.workspaceSlug === "string" ? r.workspaceSlug : ""
8138
8436
  });
8139
8437
  }
8140
- return out;
8438
+ return { ok: true, repos: out };
8439
+ }
8440
+ function describeReposFailure(kind, detail) {
8441
+ const head = "[overmind] \u043D\u0435 \u0443\u0437\u043D\u0430\u043B \u0443 \u043F\u0440\u043E\u0434\u0430, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443 \u0440\u043E\u044F (swarm_my_repos) \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435. ";
8442
+ switch (kind) {
8443
+ case "missing_tool":
8444
+ return `${head}\u041F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u0447\u0442\u043E \u0442\u0430\u043A\u043E\u0439 \u0440\u0443\u0447\u043A\u0438 \u0443 \u043D\u0435\u0433\u043E \u043D\u0435\u0442 (${detail}) \u2014 \u0432\u043E\u0442 \u044D\u0442\u043E \u0438 \u0435\u0441\u0442\u044C \xAB\u0441\u0435\u0440\u0432\u0435\u0440 \u0441\u0442\u0430\u0440\u0435\u0435 \u0434\u0435\u043C\u043E\u043D\u0430\xBB, \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u043F\u0440\u043E\u0434\u0430.`;
8445
+ case "unauthorized":
8446
+ return `${head}\u041F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0438 \u041D\u0415 \u041F\u0423\u0421\u0422\u0418\u041B: \u0442\u043E\u043A\u0435\u043D \u043D\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u0435\u043D (${detail}). \u041B\u0435\u0447\u0438\u0442\u0441\u044F \u043F\u0435\u0440\u0435\u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439: swarm login.`;
8447
+ case "offline":
8448
+ return `${head}\u0412\u042B\u0417\u041E\u0412 \u041D\u0415 \u0414\u041E\u0428\u0401\u041B \u0434\u043E \u043F\u0440\u043E\u0434\u0430 \u2014 \u0441\u0432\u044F\u0437\u044C \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C (${detail}). \u042D\u0442\u043E \u041D\u0415 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u043D\u0435 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0430\u044F \u0440\u0443\u0447\u043A\u0430: \u043F\u043E\u0432\u0442\u043E\u0440\u044E \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0442\u0438\u043A\u0435, \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u043F\u0440\u043E\u0434 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E.`;
8449
+ case "malformed":
8450
+ return `${head}\u041F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0442\u0438\u043B, \u043D\u043E \u0432 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E\u043C \u0444\u043E\u0440\u043C\u0430\u0442\u0435 (${detail}).`;
8451
+ case "unknown":
8452
+ return `${head}\u041F\u041E\u0427\u0415\u041C\u0423 \u2014 \u041D\u0415\u0418\u0417\u0412\u0415\u0421\u0422\u041D\u041E (${detail}). \u041D\u0438 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u0441\u0435\u0440\u0432\u0435\u0440, \u043D\u0438 \u043E\u0431\u0440\u044B\u0432 \u0441\u0432\u044F\u0437\u0438 \u043E\u0442\u0441\u044E\u0434\u0430 \u043D\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0442: \u0441\u043C\u043E\u0442\u0440\u0438 \u0441\u0442\u0440\u043E\u043A\u0443 \xABSwarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E \u2026\xBB \u0432\u044B\u0448\u0435 \u2014 \u0442\u0430\u043C \u043F\u0440\u0438\u0447\u0438\u043D\u0430, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u0432\u0438\u0434\u0435\u043B \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440.`;
8453
+ default:
8454
+ return `${head}\u041F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0442\u043A\u0430\u0437\u043E\u043C: ${detail}.`;
8455
+ }
8141
8456
  }
8142
8457
  function emptyRepoMapState(roots) {
8143
8458
  return { clones: [], missing: [], roots: [...roots], at: null, unknown: true };
@@ -8147,7 +8462,8 @@ async function refreshRepoMap(opts) {
8147
8462
  });
8148
8463
  const scan = opts.scan ?? scanRootsAll;
8149
8464
  const repoAt = opts.repoAt ?? repoFullNameAt;
8150
- const wanted = await fetchSwarmRepos(opts.call);
8465
+ const answer = await fetchSwarmRepos(opts.call);
8466
+ const wanted = answer.ok ? answer.repos : null;
8151
8467
  const clones = /* @__PURE__ */ new Map();
8152
8468
  const take = (repoFullName, path, source, alternatives = []) => {
8153
8469
  if (clones.has(repoFullName)) return;
@@ -8182,10 +8498,8 @@ async function refreshRepoMap(opts) {
8182
8498
  opts.cachePath,
8183
8499
  [...clones.values()].map((c) => ({ repoFullName: c.repoFullName, path: c.path }))
8184
8500
  );
8185
- if (!wanted) {
8186
- log(
8187
- "[overmind] \u043F\u0440\u043E\u0434 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443 \u0440\u043E\u044F (swarm_my_repos) \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435. \u0421\u0435\u0440\u0432\u0435\u0440 \u0441\u0442\u0430\u0440\u0435\u0435 \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u044D\u0442\u043E \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u043E \u0438 \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u043F\u0440\u043E\u0434\u0430."
8188
- );
8501
+ if (!answer.ok) {
8502
+ log(describeReposFailure(answer.kind, answer.detail));
8189
8503
  return registry;
8190
8504
  }
8191
8505
  opts.state.unknown = false;
@@ -8199,7 +8513,7 @@ async function refreshRepoMap(opts) {
8199
8513
  }
8200
8514
  log(`[overmind] \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0440\u043E\u044F (${wantedNames.length}), \u043A\u0430\u0440\u0442\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435:`);
8201
8515
  for (const c of opts.state.clones) log(`[overmind] ${describeClone(c)}`);
8202
- const prefixOf = new Map(wanted.map((r) => [r.repoFullName, r.taskPrefix]));
8516
+ const prefixOf = new Map(answer.repos.map((r) => [r.repoFullName, r.taskPrefix]));
8203
8517
  for (const name of opts.state.missing) {
8204
8518
  const prefix = prefixOf.get(name);
8205
8519
  log(`[overmind] ${describeMissing(prefix ? `${name} (\u043F\u0440\u043E\u0435\u043A\u0442 ${prefix})` : name, opts.roots)}`);
@@ -8228,7 +8542,7 @@ function repoMapRows(state) {
8228
8542
  }
8229
8543
  function readRepoMapCache(path) {
8230
8544
  try {
8231
- const raw = JSON.parse(readFileSync8(path, "utf8"));
8545
+ const raw = JSON.parse(readFileSync9(path, "utf8"));
8232
8546
  if (!Array.isArray(raw?.entries)) return [];
8233
8547
  return raw.entries.filter(
8234
8548
  (e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
@@ -8240,7 +8554,7 @@ function readRepoMapCache(path) {
8240
8554
  function writeRepoMapCache(path, entries) {
8241
8555
  try {
8242
8556
  const tmp = `${path}.tmp`;
8243
- writeFileSync7(tmp, JSON.stringify({ at: stamp(), entries }, null, 2));
8557
+ writeFileSync8(tmp, JSON.stringify({ at: stamp(), entries }, null, 2));
8244
8558
  renameSync5(tmp, path);
8245
8559
  } catch {
8246
8560
  }
@@ -8248,17 +8562,17 @@ function writeRepoMapCache(path, entries) {
8248
8562
 
8249
8563
  // src/hostIdentity.ts
8250
8564
  import { hostname } from "os";
8251
- import { existsSync as existsSync7, readFileSync as readFileSync9, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "fs";
8252
- import { join as join10 } from "path";
8565
+ import { existsSync as existsSync7, readFileSync as readFileSync10, renameSync as renameSync6, writeFileSync as writeFileSync9 } from "fs";
8566
+ import { join as join11 } from "path";
8253
8567
  import { randomUUID as randomUUID2 } from "crypto";
8254
8568
  function hostIdentityPath(home) {
8255
- return join10(home, "host.json");
8569
+ return join11(home, "host.json");
8256
8570
  }
8257
8571
  function loadHostIdentity(deps) {
8258
8572
  const exists = deps.exists ?? existsSync7;
8259
- const readFile2 = deps.readFile ?? ((p) => readFileSync9(p, "utf8"));
8573
+ const readFile2 = deps.readFile ?? ((p) => readFileSync10(p, "utf8"));
8260
8574
  const writeFile3 = deps.writeFile ?? ((p, data) => {
8261
- writeFileSync8(`${p}.tmp`, data);
8575
+ writeFileSync9(`${p}.tmp`, data);
8262
8576
  renameSync6(`${p}.tmp`, p);
8263
8577
  });
8264
8578
  const host = deps.hostname ?? hostname;
@@ -8444,12 +8758,12 @@ import { spawn } from "child_process";
8444
8758
  import { closeSync as closeSync2, existsSync as existsSync8, mkdirSync as mkdirSync6, openSync as openSync2, readdirSync as readdirSync7 } from "fs";
8445
8759
  import { createRequire } from "module";
8446
8760
  import { homedir as homedir4 } from "os";
8447
- import { dirname as dirname2, join as join12 } from "path";
8761
+ import { dirname as dirname2, join as join13 } from "path";
8448
8762
  import { fileURLToPath } from "url";
8449
8763
 
8450
8764
  // src/droneHome.ts
8451
8765
  import { lstatSync, mkdirSync as mkdirSync5, readdirSync as readdirSync6, rmSync, statSync as statSync6, symlinkSync as symlinkSync2 } from "fs";
8452
- import { join as join11, relative, sep } from "path";
8766
+ import { join as join12, relative, sep } from "path";
8453
8767
  var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
8454
8768
  function droneHomeDeny(env = process.env) {
8455
8769
  const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -8473,20 +8787,20 @@ function buildDroneHome(opts) {
8473
8787
  if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
8474
8788
  for (const existing of readdirSync6(droneHome)) {
8475
8789
  if (wanted.has(existing)) continue;
8476
- const p = join11(droneHome, existing);
8790
+ const p = join12(droneHome, existing);
8477
8791
  try {
8478
8792
  if (lstatSync(p).isSymbolicLink()) rmSync(p);
8479
8793
  } catch {
8480
8794
  }
8481
8795
  }
8482
8796
  for (const name of wanted) {
8483
- const link = join11(droneHome, name);
8797
+ const link = join12(droneHome, name);
8484
8798
  try {
8485
8799
  lstatSync(link);
8486
8800
  continue;
8487
8801
  } catch {
8488
8802
  }
8489
- const target = join11(realHome, name);
8803
+ const target = join12(realHome, name);
8490
8804
  let isDir;
8491
8805
  try {
8492
8806
  isDir = statSync6(target).isDirectory();
@@ -8507,7 +8821,7 @@ var REAL_PATH_ENV_PINS = [
8507
8821
  { env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
8508
8822
  ];
8509
8823
  function realPathEnvPins(realHome) {
8510
- return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join11(realHome, ...segments)]));
8824
+ return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join12(realHome, ...segments)]));
8511
8825
  }
8512
8826
  function dronePrivatePaths(realHome, swarmHome) {
8513
8827
  return [
@@ -8605,7 +8919,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
8605
8919
  const env = { ...base3 };
8606
8920
  const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
8607
8921
  if (isolate && !base3.TURBO_CACHE_DIR) {
8608
- env.TURBO_CACHE_DIR = join12(worktree, ".turbo", "cache");
8922
+ env.TURBO_CACHE_DIR = join13(worktree, ".turbo", "cache");
8609
8923
  }
8610
8924
  Object.assign(env, droneTestEnv(droneCap, base3));
8611
8925
  for (const key of DAEMON_STATE_ENV) delete env[key];
@@ -8633,16 +8947,16 @@ var DAEMON_STATE_ENV = [
8633
8947
  "TASKLESS_TOKEN"
8634
8948
  ];
8635
8949
  function bundledRoleDir(kind) {
8636
- return join12(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
8950
+ return join13(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
8637
8951
  }
8638
8952
  function roleDir(worktree, kind) {
8639
- return join12(worktree, ".claude", "roles", kind);
8953
+ return join13(worktree, ".claude", "roles", kind);
8640
8954
  }
8641
8955
  function roleDirs(worktree, kind) {
8642
8956
  return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync8(dir));
8643
8957
  }
8644
8958
  function nextRunLogPath(runLogsDir, runId) {
8645
- const dir = join12(runLogsDir, String(runId));
8959
+ const dir = join13(runLogsDir, String(runId));
8646
8960
  mkdirSync6(dir, { recursive: true });
8647
8961
  let files = [];
8648
8962
  try {
@@ -8654,7 +8968,7 @@ function nextRunLogPath(runLogsDir, runId) {
8654
8968
  const m = /^(\d+)\.json/.exec(f);
8655
8969
  if (m) next = Math.max(next, Number(m[1]) + 1);
8656
8970
  }
8657
- return join12(dir, `${next}.json`);
8971
+ return join13(dir, `${next}.json`);
8658
8972
  }
8659
8973
  function openNextRunLog(runLogsDir, runId) {
8660
8974
  for (let attempt = 0; attempt < 50; attempt++) {
@@ -8722,6 +9036,9 @@ function makeLauncher(cfg) {
8722
9036
  SWARM_RUN_NAME: run.name,
8723
9037
  ...run.workspace_id ? { SWARM_WORKSPACE_ID: run.workspace_id } : {},
8724
9038
  ...run.reviews_run_id != null ? { SWARM_REVIEWS_RUN_ID: String(run.reviews_run_id) } : {},
9039
+ // TASWM-344: та же роль, что у SWARM_REVIEWS_RUN_ID выше, для верификатора — без неё
9040
+ // его done() в client-режиме пошёл бы веткой обычной сдачи PR, и вердикт пропал бы.
9041
+ ...run.verifies_run_id != null ? { SWARM_VERIFIES_RUN_ID: String(run.verifies_run_id) } : {},
8725
9042
  ...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
8726
9043
  }
8727
9044
  };
@@ -8734,9 +9051,9 @@ function makeLauncher(cfg) {
8734
9051
  }
8735
9052
  const realHome = cfg.droneIsolation?.realHome ?? homedir4();
8736
9053
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
8737
- if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join12(cfg.droneIsolation.swarmHome, "token"))) {
9054
+ if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join13(cfg.droneIsolation.swarmHome, "token"))) {
8738
9055
  console.error(
8739
- `[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join12(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
9056
+ `[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join13(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
8740
9057
  );
8741
9058
  }
8742
9059
  const executor = cfg.executor ?? resolveExecutor();
@@ -8792,8 +9109,8 @@ init_procCleanup();
8792
9109
 
8793
9110
  // src/cost.ts
8794
9111
  init_executors();
8795
- import { readFileSync as readFileSync10, readdirSync as readdirSync8, renameSync as renameSync7 } from "fs";
8796
- import { join as join13 } from "path";
9112
+ import { readFileSync as readFileSync11, readdirSync as readdirSync8, renameSync as renameSync7 } from "fs";
9113
+ import { join as join14 } from "path";
8797
9114
  var UNPROCESSED_LOG_RE = /^\d+\.json$/;
8798
9115
  function unprocessedLogFiles(dir) {
8799
9116
  try {
@@ -8803,7 +9120,7 @@ function unprocessedLogFiles(dir) {
8803
9120
  }
8804
9121
  }
8805
9122
  function accountRunCost(deps, run) {
8806
- const dir = join13(deps.runLogsDir, String(run.id));
9123
+ const dir = join14(deps.runLogsDir, String(run.id));
8807
9124
  const files = unprocessedLogFiles(dir);
8808
9125
  if (files.length === 0) return;
8809
9126
  const model = resolveRunModel(run, deps.swarmModels?.() ?? null);
@@ -8814,10 +9131,10 @@ function accountRunCost(deps, run) {
8814
9131
  let durationMs = 0;
8815
9132
  const processed = [];
8816
9133
  for (const f of files) {
8817
- const full = join13(dir, f);
9134
+ const full = join14(dir, f);
8818
9135
  let raw = null;
8819
9136
  try {
8820
- raw = readFileSync10(full, "utf8");
9137
+ raw = readFileSync11(full, "utf8");
8821
9138
  } catch {
8822
9139
  continue;
8823
9140
  }
@@ -8854,14 +9171,14 @@ function makeCostAccountant(deps) {
8854
9171
 
8855
9172
  // src/runOutcome.ts
8856
9173
  init_executors();
8857
- import { readdirSync as readdirSync9, readFileSync as readFileSync11 } from "fs";
8858
- import { join as join14 } from "path";
9174
+ import { readdirSync as readdirSync9, readFileSync as readFileSync12 } from "fs";
9175
+ import { join as join15 } from "path";
8859
9176
  function endedItself(outcome) {
8860
9177
  return outcome === "graceful" || outcome === "budget";
8861
9178
  }
8862
9179
  var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
8863
9180
  function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
8864
- const dir = join14(runLogsDir, String(runId));
9181
+ const dir = join15(runLogsDir, String(runId));
8865
9182
  let files;
8866
9183
  try {
8867
9184
  files = readdirSync9(dir);
@@ -8878,7 +9195,7 @@ function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
8878
9195
  if (!latest) return "unknown";
8879
9196
  let raw;
8880
9197
  try {
8881
- raw = readFileSync11(join14(dir, latest.name), "utf8");
9198
+ raw = readFileSync12(join15(dir, latest.name), "utf8");
8882
9199
  } catch {
8883
9200
  return "unknown";
8884
9201
  }
@@ -8896,20 +9213,20 @@ init_src();
8896
9213
  init_git();
8897
9214
 
8898
9215
  // src/deps.ts
8899
- import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync12 } from "fs";
8900
- import { join as join17 } from "path";
9216
+ import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync13 } from "fs";
9217
+ import { join as join18 } from "path";
8901
9218
  import { execa } from "execa";
8902
9219
 
8903
9220
  // src/prepareLock.ts
8904
9221
  import { createHash } from "crypto";
8905
9222
  import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as statSync8, writeSync } from "fs";
8906
- import { join as join16 } from "path";
9223
+ import { join as join17 } from "path";
8907
9224
  import { tmpdir } from "os";
8908
9225
  var WAIT_TIMEOUT_MS = 5 * 6e4;
8909
9226
  var POLL_MS = 300;
8910
9227
  var STALE_MS = 20 * 6e4;
8911
9228
  function machineLockPath(key, dir = tmpdir()) {
8912
- return join16(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
9229
+ return join17(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
8913
9230
  }
8914
9231
  function defaultTryCreate(path, content) {
8915
9232
  try {
@@ -8983,7 +9300,7 @@ async function runCommand(cmd, worktree) {
8983
9300
  }
8984
9301
  function safeReadText(p) {
8985
9302
  try {
8986
- return readFileSync12(p, "utf8");
9303
+ return readFileSync13(p, "utf8");
8987
9304
  } catch {
8988
9305
  return null;
8989
9306
  }
@@ -9025,19 +9342,19 @@ function overridePlan(override) {
9025
9342
  function detectPackageManager(worktree, io = {}) {
9026
9343
  const exists = io.exists ?? existsSync10;
9027
9344
  const readText = io.readText ?? safeReadText;
9028
- const hasPkgJson = exists(join17(worktree, "package.json"));
9345
+ const hasPkgJson = exists(join18(worktree, "package.json"));
9029
9346
  if (!hasPkgJson) {
9030
- if (exists(join17(worktree, "deno.json")) || exists(join17(worktree, "deno.jsonc")) || exists(join17(worktree, "deno.lock"))) return "deno";
9347
+ if (exists(join18(worktree, "deno.json")) || exists(join18(worktree, "deno.jsonc")) || exists(join18(worktree, "deno.lock"))) return "deno";
9031
9348
  return null;
9032
9349
  }
9033
- const pkg = readJson(readText, join17(worktree, "package.json"));
9350
+ const pkg = readJson(readText, join18(worktree, "package.json"));
9034
9351
  const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
9035
9352
  if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
9036
- if (exists(join17(worktree, "bun.lock")) || exists(join17(worktree, "bun.lockb"))) return "bun";
9037
- if (exists(join17(worktree, "pnpm-lock.yaml"))) return "pnpm";
9038
- if (exists(join17(worktree, "yarn.lock"))) return "yarn";
9039
- if (exists(join17(worktree, "package-lock.json"))) return "npm";
9040
- if (exists(join17(worktree, "deno.lock"))) return "deno";
9353
+ if (exists(join18(worktree, "bun.lock")) || exists(join18(worktree, "bun.lockb"))) return "bun";
9354
+ if (exists(join18(worktree, "pnpm-lock.yaml"))) return "pnpm";
9355
+ if (exists(join18(worktree, "yarn.lock"))) return "yarn";
9356
+ if (exists(join18(worktree, "package-lock.json"))) return "npm";
9357
+ if (exists(join18(worktree, "deno.lock"))) return "deno";
9041
9358
  return "npm";
9042
9359
  }
9043
9360
  function jsInstallCommand(pm) {
@@ -9098,7 +9415,7 @@ function detectPlan(worktree, io) {
9098
9415
  // node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
9099
9416
  // У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
9100
9417
  // заново, но он дешёвый: тянет из того же общего кэша.
9101
- { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join17(worktree, "node_modules"), source: "detect" }
9418
+ { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join18(worktree, "node_modules"), source: "detect" }
9102
9419
  ];
9103
9420
  for (const target of workspaceBuildTargets(worktree, io)) {
9104
9421
  commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
@@ -9107,12 +9424,12 @@ function detectPlan(worktree, io) {
9107
9424
  return { source: "detect", ecosystem: `node/${pm}`, commands };
9108
9425
  }
9109
9426
  for (const marker of ECOSYSTEM_MARKERS) {
9110
- const hit = marker.files.some((f) => exists(join17(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
9427
+ const hit = marker.files.some((f) => exists(join18(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
9111
9428
  if (!hit) continue;
9112
9429
  let ecosystem = marker.ecosystem;
9113
9430
  let command = marker.command;
9114
9431
  for (const refine of marker.refine ?? []) {
9115
- if (!exists(join17(worktree, refine.file))) continue;
9432
+ if (!exists(join18(worktree, refine.file))) continue;
9116
9433
  ecosystem = refine.ecosystem;
9117
9434
  command = refine.command;
9118
9435
  break;
@@ -9169,12 +9486,12 @@ var CI_INSTALL_PATTERNS = [
9169
9486
  function ciWorkflowPlan(worktree, io) {
9170
9487
  const readText = io.readText ?? safeReadText;
9171
9488
  const listFiles = io.listFiles ?? safeListFiles;
9172
- const dir = join17(worktree, ".github", "workflows");
9489
+ const dir = join18(worktree, ".github", "workflows");
9173
9490
  const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
9174
9491
  const seen = /* @__PURE__ */ new Set();
9175
9492
  const commands = [];
9176
9493
  for (const name of files.sort()) {
9177
- const yaml = readText(join17(dir, name));
9494
+ const yaml = readText(join18(dir, name));
9178
9495
  if (yaml === null) continue;
9179
9496
  for (const raw of workflowRunLines(yaml)) {
9180
9497
  const line = raw.replace(/\s+/g, " ").trim();
@@ -9189,7 +9506,7 @@ function ciWorkflowPlan(worktree, io) {
9189
9506
  return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
9190
9507
  }
9191
9508
  function workspaceGlobs(worktree, readText) {
9192
- const yaml = readText(join17(worktree, "pnpm-workspace.yaml"));
9509
+ const yaml = readText(join18(worktree, "pnpm-workspace.yaml"));
9193
9510
  if (yaml !== null) {
9194
9511
  const globs = [];
9195
9512
  let inPackages = false;
@@ -9209,7 +9526,7 @@ function workspaceGlobs(worktree, readText) {
9209
9526
  }
9210
9527
  if (globs.length) return globs;
9211
9528
  }
9212
- const pkg = readJson(readText, join17(worktree, "package.json"));
9529
+ const pkg = readJson(readText, join18(worktree, "package.json"));
9213
9530
  const ws = pkg?.workspaces;
9214
9531
  const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
9215
9532
  return list.filter((g) => typeof g === "string");
@@ -9220,14 +9537,14 @@ function workspaceDirs(worktree, globs, io) {
9220
9537
  if (glob.startsWith("!")) continue;
9221
9538
  const star = glob.indexOf("*");
9222
9539
  if (star === -1) {
9223
- const dir = join17(worktree, glob);
9224
- if (io.exists(join17(dir, "package.json"))) dirs.add(dir);
9540
+ const dir = join18(worktree, glob);
9541
+ if (io.exists(join18(dir, "package.json"))) dirs.add(dir);
9225
9542
  continue;
9226
9543
  }
9227
- const base3 = join17(worktree, glob.slice(0, star).replace(/\/$/, ""));
9544
+ const base3 = join18(worktree, glob.slice(0, star).replace(/\/$/, ""));
9228
9545
  for (const name of io.listDirs(base3)) {
9229
- const dir = join17(base3, name);
9230
- if (io.exists(join17(dir, "package.json"))) dirs.add(dir);
9546
+ const dir = join18(base3, name);
9547
+ if (io.exists(join18(dir, "package.json"))) dirs.add(dir);
9231
9548
  }
9232
9549
  }
9233
9550
  return [...dirs];
@@ -9255,11 +9572,11 @@ function workspaceBuildTargets(worktree, io = {}) {
9255
9572
  if (!globs.length) return [];
9256
9573
  const pkgs = [];
9257
9574
  for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
9258
- const pkg = readJson(readText, join17(dir, "package.json"));
9575
+ const pkg = readJson(readText, join18(dir, "package.json"));
9259
9576
  if (!pkg || typeof pkg.name !== "string") continue;
9260
9577
  const scripts = pkg.scripts ?? {};
9261
9578
  const entries = entryPaths(pkg);
9262
- const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join17(dir, e)));
9579
+ const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join18(dir, e)));
9263
9580
  const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
9264
9581
  pkgs.push({ name: pkg.name, dir, deps, needsBuild });
9265
9582
  }
@@ -9287,7 +9604,7 @@ function devDatabaseUrl(worktree, io = {}) {
9287
9604
  const fromEnv = (io.env ?? process.env).DATABASE_URL;
9288
9605
  if (fromEnv && fromEnv.trim()) return fromEnv.trim();
9289
9606
  for (const file of [".env", ".env.example"]) {
9290
- const raw = readText(join17(worktree, file));
9607
+ const raw = readText(join18(worktree, file));
9291
9608
  if (raw === null) continue;
9292
9609
  const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
9293
9610
  const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
@@ -9307,7 +9624,7 @@ function prismaPackages(worktree, io) {
9307
9624
  const readText = io.readText ?? safeReadText;
9308
9625
  const listDirs = io.listDirs ?? safeListDirs;
9309
9626
  const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
9310
- return dirs.filter((dir) => exists(join17(dir, "prisma", "schema.prisma")) && listDirs(join17(dir, "prisma", "migrations")).length > 0);
9627
+ return dirs.filter((dir) => exists(join18(dir, "prisma", "schema.prisma")) && listDirs(join18(dir, "prisma", "migrations")).length > 0);
9311
9628
  }
9312
9629
  function jsExecBin(pm, command) {
9313
9630
  switch (pm) {
@@ -9508,6 +9825,23 @@ ${notice}` : built;
9508
9825
  });
9509
9826
  }
9510
9827
 
9828
+ // src/spawnDefer.ts
9829
+ function noteDefer(episodes, key, reason, heartbeatTicks) {
9830
+ const prev = episodes.get(key);
9831
+ const ticks = (prev?.ticks ?? 0) + 1;
9832
+ const first = prev === void 0;
9833
+ const changed = first || reason !== prev.reason;
9834
+ const beat = !changed && heartbeatTicks > 0 && ticks % heartbeatTicks === 0;
9835
+ episodes.set(key, { ticks, reason });
9836
+ return { log: changed || beat, changed, ticks };
9837
+ }
9838
+ function clearDefer(episodes, key) {
9839
+ return episodes.delete(key);
9840
+ }
9841
+ function pruneDefers(episodes, live) {
9842
+ for (const key of episodes.keys()) if (!live.has(key)) episodes.delete(key);
9843
+ }
9844
+
9511
9845
  // src/overmind.ts
9512
9846
  init_git();
9513
9847
 
@@ -9596,6 +9930,13 @@ function verdictLabel(verdict) {
9596
9930
  return verdict === "approve" ? "\u2705 APPROVE" : "\u274C REQUEST CHANGES";
9597
9931
  }
9598
9932
  async function latestVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
9933
+ return scanPrComments(prUrl, sinceIso, run, (body) => {
9934
+ const m = body.match(VERDICT_RE);
9935
+ if (!m) return null;
9936
+ return APPROVE_RE.test(m[1]) ? "approve" : "changes";
9937
+ });
9938
+ }
9939
+ async function scanPrComments(prUrl, sinceIso, run, pick3) {
9599
9940
  if (!isGithubPrUrl(prUrl)) return null;
9600
9941
  let out;
9601
9942
  try {
@@ -9615,13 +9956,15 @@ async function latestVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
9615
9956
  const c = comments[i];
9616
9957
  if (!c?.body || !c.createdAt) continue;
9617
9958
  if (Number.isFinite(sinceMs) && Date.parse(c.createdAt) < sinceMs) continue;
9618
- const m = c.body.match(VERDICT_RE);
9619
- if (!m) continue;
9620
- const verdict = APPROVE_RE.test(m[1]) ? "approve" : "changes";
9959
+ const verdict = pick3(c.body);
9960
+ if (verdict == null) continue;
9621
9961
  return { verdict, createdAt: c.createdAt };
9622
9962
  }
9623
9963
  return null;
9624
9964
  }
9965
+ async function latestVerifyVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
9966
+ return scanPrComments(prUrl, sinceIso, run, (body) => parseVerifyVerdictComment(body));
9967
+ }
9625
9968
  var realPrCommitsRunner = async (prUrl) => (await $3`gh pr view ${prUrl} --json commits`).stdout;
9626
9969
  async function prHasCommitsSince(prUrl, sinceIso, run = realPrCommitsRunner) {
9627
9970
  if (!isGithubPrUrl(prUrl)) return false;
@@ -9762,6 +10105,7 @@ init_attachments();
9762
10105
  init_shotUpload();
9763
10106
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9764
10107
  import { z } from "zod";
10108
+ init_src();
9765
10109
 
9766
10110
  // src/reviewCycle.ts
9767
10111
  function reviewTransitionPatch(run, prUrl) {
@@ -9835,6 +10179,7 @@ async function buildDroneServer(store, opts = {}) {
9835
10179
  const overmindDead = async () => !await store.alive();
9836
10180
  const slugForInstruction = (fallback) => opts.expectedTaskSlug ?? fallback ?? "<slug>";
9837
10181
  const iAmReviewer = (run) => run?.reviews_run_id != null || run?.kind === "reviewer" || opts.reviewsRunId != null || opts.runKind === "reviewer";
10182
+ const iAmVerifier = (run) => run?.verifies_run_id != null || run?.kind === "verifier" || opts.verifiesRunId != null || opts.runKind === "verifier";
9838
10183
  const freeze = async (withdraw = false) => {
9839
10184
  let slug;
9840
10185
  let branch;
@@ -10123,7 +10468,9 @@ ${notFoundText}` : notFoundText }],
10123
10468
  "\u0420\u0430\u0431\u043E\u0442\u0430 \u0441\u0434\u0430\u043D\u0430. \u0421 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u043C GitHub-PR (github.com/OWNER/REPO/pull/N) \u2014 \u043F\u0440\u043E\u0433\u043E\u043D \u0443\u0445\u043E\u0434\u0438\u0442 \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u0440\u043E\u0439 \u0434\u043E\u043C\u0435\u0440\u0436\u0438\u0442. \u0411\u0435\u0437 \u0441\u0432\u043E\u0435\u0433\u043E PR (\u0440\u0435\u0432\u044C\u044E/meta-\u0437\u0430\u0434\u0430\u0447\u0430) \u2014 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443; \u043C\u043E\u0436\u0435\u0448\u044C \u043F\u0435\u0440\u0435\u0434\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0443. summary \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u0438 \u0443 \u0418\u0421\u041F\u041E\u041B\u041D\u0418\u0422\u0415\u041B\u042F, \u0438 \u0443 \u0420\u0415\u0412\u042C\u042E\u0415\u0420\u0410 \u2014 \u043E\u043D \u0443\u0445\u043E\u0434\u0438\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 (TASWM-259). \u0414\u0440\u043E\u043D-\u0420\u0415\u0412\u042C\u042E\u0415\u0420 (\u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E, TASWM-148) \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u043E\u0431\u044F\u0437\u0430\u043D \u043F\u0435\u0440\u0435\u0434\u0430\u0442\u044C verdict \u2014 pr_url \u0442\u043E\u0433\u0434\u0430 \u0442\u0430 \u0436\u0435 \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 PR.",
10124
10469
  {
10125
10470
  pr_url: z.string().describe("\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0439 GitHub-PR; \u0434\u043B\u044F \u0437\u0430\u0434\u0430\u0447 \u0431\u0435\u0437 PR \u2014 \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043F\u0440\u043E\u0433\u043E\u043D \u043F\u0440\u043E\u0441\u0442\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044F)"),
10126
- verdict: z.enum(["approve", "changes"]).optional().describe("\u0422\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0434\u0440\u043E\u043D\u0430-\u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430: \u0438\u0442\u043E\u0433 \u043F\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u043E\u043C\u0443 PR \u2014 approve (APPROVE) \u0438\u043B\u0438 changes (REQUEST CHANGES). \u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u0434\u043B\u044F \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430."),
10471
+ verdict: z.enum(["approve", "changes", ...VERIFY_VERDICTS]).optional().describe(
10472
+ "\u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430. \u0420\u0435\u0432\u044C\u044E\u0435\u0440 (\u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E): approve | changes. \u0412\u0415\u0420\u0418\u0424\u0418\u041A\u0410\u0422\u041E\u0420 (TASWM-344): confirmed (\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E) | not_reproduced (\u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0441\u044F) | found_issues (\u043D\u0430\u0448\u0451\u043B \u0432\u043E\u0442 \u044D\u0442\u043E). \u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u0434\u043B\u044F \u043E\u0431\u043E\u0438\u0445."
10473
+ ),
10127
10474
  summary: z.string().optional().describe(
10128
10475
  "\u041E\u0442\u0447\u0451\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443, \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u043F\u0440\u0438 \u0441\u0434\u0430\u0447\u0435 \u0441 PR. \u0418\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C: \u0447\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u043D\u043E, \u043A\u0430\u043A\u0438\u0435 \u0444\u0430\u0439\u043B\u044B \u0437\u0430\u0442\u0440\u043E\u043D\u0443\u0442\u044B, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043F\u0440\u0438\u043D\u044F\u043B \u0438 \u0447\u0442\u043E \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C \u0437\u0430 \u0441\u043A\u043E\u0431\u043A\u0430\u043C\u0438. \u0420\u0435\u0432\u044C\u044E\u0435\u0440: \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u043D\u0430\u0448\u0451\u043B."
10129
10476
  ),
@@ -10226,6 +10573,66 @@ ${notFoundText}` : notFoundText }],
10226
10573
  \u26A0\uFE0F \u0412\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u041D\u0415 \u0443\u0435\u0445\u0430\u043B (${commentWarning}) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0435\u0441\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0435. \u041F\u0440\u043E\u0434\u0443\u0431\u043B\u0438\u0440\u0443\u0439 \u0435\u0433\u043E \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR: \`gh pr comment ${trustedPrUrl} --body "${verdictText} \u2026"\`.` : "";
10227
10574
  return { content: [{ type: "text", text: `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0437\u0430\u043F\u0438\u0441\u0430\u043D, \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E${shotsNote(shots)}${commentNote}` }] };
10228
10575
  }
10576
+ if (iAmVerifier(run) && run?.verifies_run_id == null) {
10577
+ return {
10578
+ content: [
10579
+ {
10580
+ type: "text",
10581
+ text: '\u0422\u044B \u0434\u0440\u043E\u043D-\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440, \u043D\u043E \u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u043C \u2014 \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u043A\u0443\u0434\u0430, \u0438 \u0437\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-344). \u041E\u0441\u0442\u0430\u0432\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR (`gh pr comment <pr> --body "**\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E**|**\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F**|**\u041D\u0410\u0428\u0401\u041B** \u2026"`) \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 done() \u0447\u0435\u0440\u0435\u0437 \u043C\u0438\u043D\u0443\u0442\u0443.'
10582
+ }
10583
+ ],
10584
+ isError: true
10585
+ };
10586
+ }
10587
+ if (run?.verifies_run_id != null) {
10588
+ if (isTerminalStatus(run.status)) {
10589
+ return { content: [{ type: "text", text: "\u043F\u0440\u043E\u0433\u043E\u043D \u0443\u0436\u0435 \u0437\u0430\u043A\u0440\u044B\u0442 (PR \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u0440\u0430\u043D\u044C\u0448\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D" }] };
10590
+ }
10591
+ if (!isVerifyVerdict(verdict)) {
10592
+ return {
10593
+ content: [
10594
+ {
10595
+ type: "text",
10596
+ text: '\u0422\u044B \u0434\u0440\u043E\u043D-\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 (TASWM-344) \u2014 done() \u0442\u0440\u0435\u0431\u0443\u0435\u0442 verdict: "confirmed" (\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E) | "not_reproduced" (\u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0441\u044F) | "found_issues" (\u043D\u0430\u0448\u0451\u043B \u0432\u043E\u0442 \u044D\u0442\u043E). \u0427\u0435\u0442\u0432\u0451\u0440\u0442\u043E\u0433\u043E \u0438\u0441\u0445\u043E\u0434\u0430 \u043D\u0435\u0442, \u0438 \u0443\u0441\u043F\u0435\u0445\u043E\u043C \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0435\u0442\u0441\u044F: \u043F\u043E\u043A\u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0435\u0439 \u043F\u0440\u0438\u0451\u043C\u043A\u0443. \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u043E\u0432\u0441\u0435 \u2014 \u044D\u0442\u043E "not_reproduced" \u0441 \u0447\u0435\u0441\u0442\u043D\u043E\u0439 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \u0432 summary, \u0430 \u043D\u0435 \u043C\u043E\u043B\u0447\u0430\u043D\u0438\u0435.'
10597
+ }
10598
+ ],
10599
+ isError: true
10600
+ };
10601
+ }
10602
+ const verified = await store.getRun(run.verifies_run_id);
10603
+ const verdictText = verifyVerdictLabel(verdict);
10604
+ await store.updateRun(run.verifies_run_id, { verify_verdict: verdict });
10605
+ await store.addEvent(run.verifies_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u043E\u0442 ${run.name}: ${verdictText}${summary ? ` \u2014 ${summary}` : ""}`);
10606
+ const verifiedSlug = reportSlug(verified?.task_slug);
10607
+ let shots = { attachments: [], warning: "" };
10608
+ let commentWarning = "";
10609
+ if (opts.taskless && verifiedSlug) {
10610
+ shots = await uploadShots();
10611
+ try {
10612
+ await opts.taskless.addComment(
10613
+ verifiedSlug,
10614
+ `\u{1F9EA} \u0412\u0415\u0420\u0414\u0418\u041A\u0422 \u0412\u0415\u0420\u0418\u0424\u0418\u041A\u0410\u0422\u041E\u0420\u0410 ${run.name}: **${verdictText}**
10615
+
10616
+ ${(summary ?? "").trim()}${shots.warning}`,
10617
+ void 0,
10618
+ shots.attachments
10619
+ );
10620
+ } catch (e) {
10621
+ commentWarning = e instanceof Error ? e.message : String(e);
10622
+ await store.addEvent(run.verifies_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 ${verifiedSlug} \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${commentWarning}`);
10623
+ }
10624
+ } else {
10625
+ commentWarning = opts.taskless ? "\u0437\u0430\u0434\u0430\u0447\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430" : "\u043A\u043B\u0438\u0435\u043D\u0442 Taskless \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D";
10626
+ await store.addEvent(run.verifies_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${commentWarning}`);
10627
+ }
10628
+ const trustedPrUrl = verified?.pr_url ?? pr_url;
10629
+ await store.updateRun(null, { status: "review", pr_url: trustedPrUrl });
10630
+ await store.addEvent(null, "pr_opened", trustedPrUrl);
10631
+ const commentNote = commentWarning ? `
10632
+
10633
+ \u26A0\uFE0F \u0412\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u041D\u0415 \u0443\u0435\u0445\u0430\u043B (${commentWarning}) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0435\u0441\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0435. \u041F\u0440\u043E\u0434\u0443\u0431\u043B\u0438\u0440\u0443\u0439 \u0435\u0433\u043E \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR: \`gh pr comment ${trustedPrUrl} --body "${verdictText} \u2026"\`.` : "";
10634
+ return { content: [{ type: "text", text: `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0437\u0430\u043F\u0438\u0441\u0430\u043D (${verdictText})${shotsNote(shots)}${commentNote}` }] };
10635
+ }
10229
10636
  if (isGithubPrUrl(pr_url)) {
10230
10637
  const report = (summary ?? "").trim();
10231
10638
  if (!report) {
@@ -10434,6 +10841,8 @@ function ensureRunHydrated(db, runId, info) {
10434
10841
  kind: info.kind,
10435
10842
  name: info.name,
10436
10843
  reviews_run_id: info.reviews_run_id,
10844
+ verifies_run_id: info.verifies_run_id,
10845
+ // TASWM-344
10437
10846
  hero_run_id: info.hero_run_id
10438
10847
  });
10439
10848
  }
@@ -10447,12 +10856,14 @@ async function runDroneServer(runId) {
10447
10856
  const expectedTaskSlug = process.env.SWARM_TASK_SLUG || void 0;
10448
10857
  const expectedKind = toRunKind(process.env.SWARM_RUN_KIND);
10449
10858
  const spawnReviewsRunId = Number(process.env.SWARM_REVIEWS_RUN_ID);
10859
+ const spawnVerifiesRunId = Number(process.env.SWARM_VERIFIES_RUN_ID);
10450
10860
  const common = {
10451
10861
  taskless,
10452
10862
  runKind: expectedKind,
10453
10863
  prHeadBranch,
10454
10864
  ...expectedTaskSlug ? { expectedTaskSlug } : {},
10455
- ...Number.isInteger(spawnReviewsRunId) && spawnReviewsRunId > 0 ? { reviewsRunId: spawnReviewsRunId } : {}
10865
+ ...Number.isInteger(spawnReviewsRunId) && spawnReviewsRunId > 0 ? { reviewsRunId: spawnReviewsRunId } : {},
10866
+ ...Number.isInteger(spawnVerifiesRunId) && spawnVerifiesRunId > 0 ? { verifiesRunId: spawnVerifiesRunId } : {}
10456
10867
  };
10457
10868
  const channelUrl = process.env.SWARM_DRONE_CHANNEL_URL;
10458
10869
  const channelToken = process.env.SWARM_DRONE_CHANNEL_TOKEN;
@@ -10473,6 +10884,7 @@ async function runDroneServer(runId) {
10473
10884
  kind: expectedKind,
10474
10885
  name: process.env.SWARM_RUN_NAME || void 0,
10475
10886
  reviews_run_id: process.env.SWARM_REVIEWS_RUN_ID ? Number(process.env.SWARM_REVIEWS_RUN_ID) : null,
10887
+ verifies_run_id: process.env.SWARM_VERIFIES_RUN_ID ? Number(process.env.SWARM_VERIFIES_RUN_ID) : null,
10476
10888
  hero_run_id: process.env.SWARM_HERO_RUN_ID ? Number(process.env.SWARM_HERO_RUN_ID) : null
10477
10889
  });
10478
10890
  const store = localDroneStore2(db, runId, { heartbeatPath: HEARTBEAT_PATH2, heartbeatStaleMs: HEARTBEAT_STALE_MS2 });
@@ -10668,6 +11080,9 @@ function joinCapped(labels) {
10668
11080
  joined = labels.slice(0, Math.max(cut, 1)).join(", ");
10669
11081
  return `${joined} \u0438 \u0435\u0449\u0451 ${labels.length - Math.max(cut, 1)}`;
10670
11082
  }
11083
+ function autoReviewOnFor(flag, repo) {
11084
+ return typeof flag === "function" ? flag(repo) : flag;
11085
+ }
10671
11086
  function buildAnomalyCandidates(db, now, thresholds, opts) {
10672
11087
  const candidates = [];
10673
11088
  const allRuns = db.listRuns().filter((r) => r.superseded_by == null);
@@ -10802,7 +11217,7 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
10802
11217
  ...hrefOf(run.task_url)
10803
11218
  })
10804
11219
  });
10805
- } else if (opts.autoReviewEnabled && run.review_verdict == null && db.reviewerCountFor(run.id) === 0) {
11220
+ } else if (autoReviewOnFor(opts.autoReviewEnabled, run.repo) && run.review_verdict == null && db.reviewerCountFor(run.id) === 0) {
10806
11221
  candidates.push({
10807
11222
  key: `pr_no_reviewer:${run.id}`,
10808
11223
  minDurationMs: thresholds.prNoReviewerMs,
@@ -10989,9 +11404,11 @@ init_droneChannel();
10989
11404
  init_procList();
10990
11405
 
10991
11406
  // src/mcpResilient.ts
11407
+ init_linkFailure();
10992
11408
  init_taskless();
10993
11409
  var DEFAULT_BACKOFF_MS = [5e3, 15e3, 3e4, 6e4];
10994
11410
  var DEFAULT_DOWN_AFTER_MS = 5 * 6e4;
11411
+ var DEFAULT_RETRY_DELAYS_MS = [200, 800];
10995
11412
  function errorResult(reason) {
10996
11413
  return { isError: true, content: [{ type: "text", text: `Swarm MCP \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${reason}` }] };
10997
11414
  }
@@ -10999,11 +11416,16 @@ function resilientSwarmMcp(opts = {}) {
10999
11416
  const connect = opts.connect ?? (() => connectSwarmMcp());
11000
11417
  const backoff = opts.backoffMs?.length ? opts.backoffMs : DEFAULT_BACKOFF_MS;
11001
11418
  const downAfterMs = opts.downAfterMs ?? DEFAULT_DOWN_AFTER_MS;
11419
+ const retryDelays = opts.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
11420
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
11002
11421
  let caller = null;
11003
11422
  let lastTryAt = 0;
11004
11423
  let failures = 0;
11005
11424
  let downSince = null;
11006
11425
  let downReported = false;
11426
+ let lastConnectReason = null;
11427
+ let lastOkAt = null;
11428
+ let lastCallReason = null;
11007
11429
  function maybeReportDown() {
11008
11430
  if (downReported || downSince === null) return;
11009
11431
  const downMs = Date.now() - downSince;
@@ -11011,45 +11433,65 @@ function resilientSwarmMcp(opts = {}) {
11011
11433
  downReported = true;
11012
11434
  opts.onDown?.(downMs);
11013
11435
  }
11014
- async function ensureCaller() {
11436
+ async function ensureCaller(force = false) {
11015
11437
  if (caller) return caller;
11016
11438
  const now = Date.now();
11017
11439
  const wait = backoff[Math.max(0, Math.min(failures - 1, backoff.length - 1))];
11018
- if (lastTryAt !== 0 && now - lastTryAt < wait) return null;
11019
- lastTryAt = now;
11440
+ if (!force && lastTryAt !== 0 && now - lastTryAt < wait) return null;
11441
+ if (!force) lastTryAt = now;
11020
11442
  let fresh = null;
11021
11443
  try {
11022
11444
  fresh = await connect();
11023
- } catch {
11445
+ if (!fresh) lastConnectReason = lastSwarmConnectFailure() ?? UNKNOWN_LINK_REASON;
11446
+ } catch (e) {
11024
11447
  fresh = null;
11448
+ lastConnectReason = describeError(e);
11025
11449
  }
11026
11450
  if (fresh) {
11027
11451
  caller = fresh;
11028
11452
  failures = 0;
11453
+ lastConnectReason = null;
11029
11454
  if (downReported) opts.onUp?.();
11030
11455
  downSince = null;
11031
11456
  downReported = false;
11032
11457
  return caller;
11033
11458
  }
11034
- failures += 1;
11459
+ if (!force) failures += 1;
11035
11460
  if (downSince === null) downSince = now;
11036
11461
  return null;
11037
11462
  }
11038
- return async (name, args) => {
11039
- const active = await ensureCaller();
11040
- if (!active) {
11041
- maybeReportDown();
11042
- return errorResult("\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E");
11043
- }
11044
- try {
11045
- return await active(name, args);
11046
- } catch (e) {
11047
- caller = null;
11048
- if (downSince === null) downSince = Date.now();
11049
- maybeReportDown();
11050
- return errorResult(e instanceof Error ? e.message : String(e));
11463
+ const call = async (name, args) => {
11464
+ for (let attempt = 0; ; attempt++) {
11465
+ const active = await ensureCaller(attempt > 0);
11466
+ if (!active) {
11467
+ maybeReportDown();
11468
+ return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON);
11469
+ }
11470
+ try {
11471
+ const res = await active(name, args);
11472
+ lastOkAt = Date.now();
11473
+ lastCallReason = null;
11474
+ return res;
11475
+ } catch (e) {
11476
+ caller = null;
11477
+ if (downSince === null) downSince = Date.now();
11478
+ const { kind, detail: reason } = explainLinkFailure(e);
11479
+ lastCallReason = reason;
11480
+ const delay = retryDelays[attempt];
11481
+ if (delay === void 0 || kind !== "offline") {
11482
+ maybeReportDown();
11483
+ return errorResult(reason);
11484
+ }
11485
+ opts.onRetry?.({ tool: name, attempt: attempt + 1, reason });
11486
+ await sleep2(delay);
11487
+ }
11051
11488
  }
11052
11489
  };
11490
+ return Object.assign(call, {
11491
+ // Причина — ТА ЖЕ, что уехала в лог: сначала свежий отказ вызова, иначе отказ
11492
+ // подключения. Своего разбора здесь нет намеренно (см. lastCallReason выше).
11493
+ link: () => ({ connected: caller !== null, downSince, lastOkAt, lastError: caller !== null ? null : lastCallReason ?? lastConnectReason })
11494
+ });
11053
11495
  }
11054
11496
 
11055
11497
  // src/overmind.ts
@@ -11085,6 +11527,73 @@ function workspaceAccessLines(input) {
11085
11527
  return lines;
11086
11528
  }
11087
11529
 
11530
+ // src/overmind.ts
11531
+ init_authState();
11532
+
11533
+ // src/swarmStatus.ts
11534
+ init_linkFailure();
11535
+
11536
+ // src/release.ts
11537
+ init_src();
11538
+ init_claudeBin();
11539
+ var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
11540
+ var LATEST_TIMEOUT_MS = 4e3;
11541
+ async function fetchLatestVersion(opts = {}) {
11542
+ const fetchFn = opts.fetchFn ?? fetch;
11543
+ const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
11544
+ const pkg = opts.pkg ?? SWARM_PACKAGE;
11545
+ const ctrl = new AbortController();
11546
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
11547
+ try {
11548
+ const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
11549
+ signal: ctrl.signal,
11550
+ headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
11551
+ });
11552
+ if (!res.ok) return null;
11553
+ const body = await res.json();
11554
+ return typeof body.version === "string" && body.version ? body.version : null;
11555
+ } catch {
11556
+ return null;
11557
+ } finally {
11558
+ clearTimeout(timer);
11559
+ }
11560
+ }
11561
+ function isOutdated(current, latest) {
11562
+ if (!latest || !current) return false;
11563
+ return compareVersionDirs(latest, current) > 0;
11564
+ }
11565
+ function outdatedLine(current, latest, updateCommand = "swarm update") {
11566
+ if (!isOutdated(current, latest)) return null;
11567
+ return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
11568
+ }
11569
+
11570
+ // src/swarmStatus.ts
11571
+ init_src();
11572
+ function classifyLinkError(e) {
11573
+ const { kind, detail } = explainLinkFailure(e);
11574
+ const short2 = detail.replace(/\s+/g, " ").trim().slice(0, 200);
11575
+ const map = {
11576
+ unauthorized: "revoked",
11577
+ missing_tool: "server",
11578
+ offline: "network",
11579
+ rejected: "server",
11580
+ unknown: "down"
11581
+ };
11582
+ return { kind: map[kind], detail: short2 };
11583
+ }
11584
+ function composeLink(input) {
11585
+ const base3 = { lastOkAt: input.transport?.lastOkAt != null ? new Date(input.transport.lastOkAt).toISOString() : null, pending: input.pending, api: input.api };
11586
+ if (input.authDenied) return { kind: "revoked", detail: input.authDenied, ...base3 };
11587
+ if (!input.transport) return { kind: "no-prod", detail: "sqlite-\u0440\u0435\u0436\u0438\u043C \u2014 \u043F\u0440\u043E\u0434\u0430 \u0443 \u044D\u0442\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435", ...base3 };
11588
+ if (input.transport.connected) return { kind: "ok", detail: "", ...base3 };
11589
+ if (input.transport.lastOkAt == null && input.transport.downSince == null && !input.transport.lastError) {
11590
+ return { kind: "starting", detail: "\u0434\u0435\u043C\u043E\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F \u2014 \u0432 \u043F\u0440\u043E\u0434 \u0435\u0449\u0451 \u043D\u0435 \u0445\u043E\u0434\u0438\u043B", ...base3 };
11591
+ }
11592
+ const why = input.transport.lastError;
11593
+ if (!why) return { kind: "down", detail: "", ...base3 };
11594
+ return { ...classifyLinkError(new Error(why)), ...base3 };
11595
+ }
11596
+
11088
11597
  // src/overmind.ts
11089
11598
  init_workspace();
11090
11599
  init_attachments();
@@ -11219,10 +11728,23 @@ function makeMergeGateResolver(deps) {
11219
11728
  const run = findRunByPr(deps.listRuns(), ref);
11220
11729
  if (!run) return { ...computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), run: null };
11221
11730
  const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
11222
- const decision = computeMergeGate({ hasRun: true, autoReview, reviewVerdict: run.review_verdict });
11731
+ const decision = computeMergeGate({
11732
+ hasRun: true,
11733
+ autoReview,
11734
+ reviewVerdict: run.review_verdict,
11735
+ autoVerify: deps.autoVerify === true,
11736
+ verifyVerdict: run.verify_verdict ?? null
11737
+ });
11223
11738
  return {
11224
11739
  ...decision,
11225
- run: { id: run.id, task_slug: run.task_slug, name: run.name, pr_url: run.pr_url, review_verdict: run.review_verdict }
11740
+ run: {
11741
+ id: run.id,
11742
+ task_slug: run.task_slug,
11743
+ name: run.name,
11744
+ pr_url: run.pr_url,
11745
+ review_verdict: run.review_verdict,
11746
+ verify_verdict: run.verify_verdict ?? null
11747
+ }
11226
11748
  };
11227
11749
  };
11228
11750
  }
@@ -11665,7 +12187,7 @@ async function changedPaths(worktree, opts = {}) {
11665
12187
  // package.json
11666
12188
  var package_default = {
11667
12189
  name: "@taskless-app/swarm",
11668
- version: "0.6.10",
12190
+ version: "0.6.12",
11669
12191
  type: "module",
11670
12192
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
11671
12193
  keywords: [
@@ -11715,7 +12237,7 @@ var package_default = {
11715
12237
  execa: "^10.0.0",
11716
12238
  zod: "3.25.76"
11717
12239
  },
11718
- gitHead: "06e2edbcc9a21f0a1ef402868ea37b2b7d591020"
12240
+ gitHead: "43c844efdbea61a27c54edc17a111d658ce6e2b4"
11719
12241
  };
11720
12242
 
11721
12243
  // src/version.ts
@@ -11727,6 +12249,7 @@ init_storm();
11727
12249
  init_resources();
11728
12250
  init_config();
11729
12251
  init_droneChannel();
12252
+ init_authState();
11730
12253
  init_oauth();
11731
12254
  var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
11732
12255
  var jsonForScript = (v) => JSON.stringify(v).replace(/</g, "\\u003c");
@@ -11735,6 +12258,15 @@ var sqliteIso = (ts) => {
11735
12258
  const ms = sqliteMs(ts);
11736
12259
  return Number.isNaN(ms) ? ts : new Date(ms).toISOString();
11737
12260
  };
12261
+ function stormFacts(db) {
12262
+ if (!db.stormActive()) return null;
12263
+ const since = db.currentStormStartedAt();
12264
+ const recs = since ? db.stormProbeRecordsSince(since) : [];
12265
+ return {
12266
+ reason: lastProbeReason(recs) === "limit" ? "limit" : "provider",
12267
+ hint: recs.length ? recs[recs.length - 1].resetHint ?? null : null
12268
+ };
12269
+ }
11738
12270
  function roySnapshot(db, now = Date.now(), overmindLog = [], paused = false, daemonVersion = null, restart = null, selfHost = null) {
11739
12271
  const all = db.listRuns();
11740
12272
  const c = (s) => all.filter((r) => r.status === s).length;
@@ -11744,11 +12276,11 @@ function roySnapshot(db, now = Date.now(), overmindLog = [], paused = false, dae
11744
12276
  if (r.status === "done" || r.status === "stopped" || r.status === "failed") return sqliteMs(r.updated_at) >= now - DAY_MS;
11745
12277
  return true;
11746
12278
  });
11747
- const stormActive = db.stormActive();
12279
+ const storm = stormFacts(db);
12280
+ const stormActive = storm !== null;
11748
12281
  const stormSince = db.currentStormStartedAt();
11749
- const stormRecs = stormActive && stormSince ? db.stormProbeRecordsSince(stormSince) : [];
11750
- const stormReason = stormActive ? lastProbeReason(stormRecs) === "limit" ? "limit" : "provider" : null;
11751
- const stormResetHint = stormRecs.length ? stormRecs[stormRecs.length - 1].resetHint ?? null : null;
12282
+ const stormReason = storm?.reason ?? null;
12283
+ const stormResetHint = storm?.hint ?? null;
11752
12284
  return {
11753
12285
  metrics: {
11754
12286
  running: c("running"),
@@ -13031,7 +13563,17 @@ function daemonStatus(deps) {
13031
13563
  const counts = {};
13032
13564
  for (const r of deps.db.listRuns()) counts[r.status] = (counts[r.status] ?? 0) + 1;
13033
13565
  const live = deps.liveSessionIds?.() ?? [];
13566
+ const ver = deps.daemonVersion?.() ?? null;
13567
+ const queuedReady = deps.db.listRuns("queued").filter((r) => deps.db.unmetDeps(r.depends_on).length === 0).length;
13034
13568
  return {
13569
+ api: deps.api ?? TASKLESS_API,
13570
+ queuedReady,
13571
+ startedAt: ver?.startedAt ?? null,
13572
+ latestVersion: ver?.latestVersion ?? null,
13573
+ link: deps.link?.() ?? null,
13574
+ questions: deps.db.openQuestions().length,
13575
+ maxDrones: deps.maxDrones?.() ?? null,
13576
+ storm: stormFacts(deps.db),
13035
13577
  hostId: deps.selfHost?.hostId ?? null,
13036
13578
  hostName: deps.selfHost?.hostName ?? null,
13037
13579
  backend: SWARM_BACKEND,
@@ -13041,7 +13583,9 @@ function daemonStatus(deps) {
13041
13583
  counts,
13042
13584
  live,
13043
13585
  version: PACKAGE_VERSION,
13044
- repos: deps.repoMap?.() ?? []
13586
+ repos: deps.repoMap?.() ?? [],
13587
+ // TASWM-324: «нас не пускают» — то же состояние процесса, что кричит рамкой в лог.
13588
+ authDenied: authStateLine()
13045
13589
  };
13046
13590
  }
13047
13591
  function eventsStream(db, narratorLog, control, daemonVersion, restart, liveSessionIds2, selfHost) {
@@ -13141,7 +13685,12 @@ function makeHandler(deps) {
13141
13685
  if (p === "/merge-gate") {
13142
13686
  const ref = url.searchParams.get("pr") ?? "";
13143
13687
  if (!ref.trim()) return Response.json({ error: "\u043D\u0443\u0436\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 pr=<url|\u043D\u043E\u043C\u0435\u0440>" }, { status: 400 });
13144
- const resolve4 = deps.mergeGate ?? makeMergeGateResolver({ listRuns: () => deps.db.listRuns(), fallbackAutoReview: SWARM_AUTO_REVIEW });
13688
+ const resolve4 = deps.mergeGate ?? makeMergeGateResolver({
13689
+ listRuns: () => deps.db.listRuns(),
13690
+ fallbackAutoReview: SWARM_AUTO_REVIEW,
13691
+ autoVerify: SWARM_AUTO_VERIFY
13692
+ // TASWM-344
13693
+ });
13145
13694
  return Response.json(await resolve4(ref));
13146
13695
  }
13147
13696
  if (p === "/resources") return Response.json(hostResourceSnapshot());
@@ -13301,40 +13850,6 @@ function closeWebServer(server) {
13301
13850
  });
13302
13851
  }
13303
13852
 
13304
- // src/release.ts
13305
- init_src();
13306
- init_claudeBin();
13307
- var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
13308
- var LATEST_TIMEOUT_MS = 4e3;
13309
- async function fetchLatestVersion(opts = {}) {
13310
- const fetchFn = opts.fetchFn ?? fetch;
13311
- const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
13312
- const pkg = opts.pkg ?? SWARM_PACKAGE;
13313
- const ctrl = new AbortController();
13314
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
13315
- try {
13316
- const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
13317
- signal: ctrl.signal,
13318
- headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
13319
- });
13320
- if (!res.ok) return null;
13321
- const body = await res.json();
13322
- return typeof body.version === "string" && body.version ? body.version : null;
13323
- } catch {
13324
- return null;
13325
- } finally {
13326
- clearTimeout(timer);
13327
- }
13328
- }
13329
- function isOutdated(current, latest) {
13330
- if (!latest || !current) return false;
13331
- return compareVersionDirs(latest, current) > 0;
13332
- }
13333
- function outdatedLine(current, latest, updateCommand = "swarm update") {
13334
- if (!isOutdated(current, latest)) return null;
13335
- return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
13336
- }
13337
-
13338
13853
  // src/daemonVersion.ts
13339
13854
  function captureDaemonStart(now = () => /* @__PURE__ */ new Date()) {
13340
13855
  return { startedAt: now().toISOString(), version: PACKAGE_VERSION };
@@ -14521,20 +15036,57 @@ async function closeMergedTaskRuns(deps) {
14521
15036
  }
14522
15037
  async function closeOrphanedReviewers(deps) {
14523
15038
  for (const rv of deps.db.listRuns()) {
14524
- if (rv.kind !== "reviewer" || rv.reviews_run_id == null) continue;
15039
+ const checkedId = rv.kind === "reviewer" ? rv.reviews_run_id : rv.kind === "verifier" ? rv.verifies_run_id : null;
15040
+ if (checkedId == null) continue;
14525
15041
  if (!REVIEWER_ALIVE_STATUSES.has(rv.status)) continue;
14526
- const parent = deps.db.getRun(rv.reviews_run_id);
15042
+ const parent = deps.db.getRun(checkedId);
14527
15043
  if (parent && !isTerminalStatus(parent.status)) continue;
14528
15044
  try {
14529
15045
  const why = parent ? `\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442 (${STATUS_META[parent.status]?.label ?? parent.status})` : "\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0438\u0441\u0447\u0435\u0437";
14530
- await closeReviewerRun(deps, rv, `${why} \u2014 PR \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u0440\u0435\u0432\u044C\u044E\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E`);
14531
- if (parent) deps.db.addEvent(parent.id, "step", `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${rv.name} (#${rv.id}) \u0437\u0430\u043A\u0440\u044B\u0442: ${why}`);
15046
+ await closeReviewerRun(deps, rv, `${why} \u2014 PR \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E`);
15047
+ if (parent) deps.db.addEvent(parent.id, "step", `${rv.kind === "verifier" ? "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440" : "\u0440\u0435\u0432\u044C\u044E\u0435\u0440"} ${rv.name} (#${rv.id}) \u0437\u0430\u043A\u0440\u044B\u0442: ${why}`);
14532
15048
  narrate(deps, `${droneLabel(rv)} \u043E\u0441\u0442\u0430\u043B\u0441\u044F \u043D\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0435 \u2014 \u0433\u0430\u0448\u0443 \u0441\u0430\u043C, \u0441\u043B\u043E\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D.`);
14533
15049
  } catch (e) {
14534
15050
  console.error(`[overmind] \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u0435 \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 #${rv.id} \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C:`, e);
14535
15051
  }
14536
15052
  }
14537
15053
  }
15054
+ var VERIFY_SPAWN_LIMIT = Number(process.env.SWARM_VERIFY_SPAWN_LIMIT) || 2;
15055
+ var VERIFY_ESCALATION_MARK = "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B";
15056
+ function countActiveVerifiers(db) {
15057
+ return db.listRuns().filter((r) => r.kind === "verifier" && ACTIVE_DRONE_STATUSES.has(r.status)).length;
15058
+ }
15059
+ async function spawnVerifiersForOpenPrs(deps, stormActive) {
15060
+ if (!deps.spawnVerifier) return;
15061
+ if (stormActive) return;
15062
+ for (const run of deps.db.listRuns("review")) {
15063
+ try {
15064
+ if (run.kind !== "drone") continue;
15065
+ if (run.reviews_run_id != null || run.resolves_run_id != null || run.verifies_run_id != null) continue;
15066
+ if (!run.pr_url || !isGithubPrUrl(run.pr_url)) continue;
15067
+ if (run.verify_verdict != null) continue;
15068
+ if (deps.db.verifierCountFor(run.id) > 0) continue;
15069
+ const attempts = deps.db.verifierAttemptCountFor(run.id);
15070
+ if (attempts >= VERIFY_SPAWN_LIMIT) {
15071
+ if (!deps.db.events(run.id).some((e) => (e.text ?? "").includes(VERIFY_ESCALATION_MARK))) {
15072
+ deps.db.addEvent(run.id, "step", `${VERIFY_ESCALATION_MARK}: ${attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`);
15073
+ narrate(deps, `\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F ${droneLabel(run)} \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0437\u0430 ${attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A.`);
15074
+ }
15075
+ continue;
15076
+ }
15077
+ if (deps.resourcesOk && !deps.resourcesOk()) continue;
15078
+ if (deps.reviewCap !== void 0 && countActiveVerifiers(deps.db) >= deps.reviewCap) continue;
15079
+ const st = await deps.prStatus(run.pr_url).catch(() => null);
15080
+ if (prLiveness(st) !== "open") continue;
15081
+ if (st?.isDraft || st?.conflicting) continue;
15082
+ await deps.spawnVerifier(run, st?.files ?? []);
15083
+ narrate(deps, `PR ${droneLabel(run)} \u043E\u0442\u043A\u0440\u044B\u0442 \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430: \u043F\u0443\u0441\u0442\u044C \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0435\u0442 \u043E\u043F\u0440\u043E\u0432\u0435\u0440\u0433\u043D\u0443\u0442\u044C \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435.`);
15084
+ } catch (e) {
15085
+ deps.db.addEvent(run.id, "step", `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C: ${String(e)}`);
15086
+ console.error(`[overmind] \u043F\u043E\u0434\u044A\u0451\u043C \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u0434\u043B\u044F #${run.id} (${run.task_slug}) \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
15087
+ }
15088
+ }
15089
+ }
14538
15090
  var CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0446\u0435\u0441\u0441) \u2014 \u0442\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log` \u0438 \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0441\u0432\u043E\u0438\u0445 \u0448\u0430\u0433\u043E\u0432: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438 \u0441 \u043C\u0435\u0441\u0442\u0430 \u043E\u0431\u0440\u044B\u0432\u0430. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u041A\u041E\u041C\u041C\u0418\u0422\u042C \u0418 \u041F\u0423\u0428\u042C \u043F\u043E\u0441\u043B\u0435 \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0448\u0430\u0433\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url). \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 MCP-\u0442\u0443\u043B ask.";
14539
15091
  function buildCiWakeNudge(summary) {
14540
15092
  return [
@@ -14711,6 +15263,16 @@ function ciTrustLine(prUrl, verifyMode) {
14711
15263
  }
14712
15264
  return `\u041D\u0435 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0439 \u0442\u0435\u0441\u0442-\u0441\u044C\u044E\u0442/typecheck \u0437\u0430\u043D\u043E\u0432\u043E \u2014 \u044D\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043B CI \u043D\u0430 \u044D\u0442\u043E\u043C \u0436\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u0435 (\u0434\u0432\u043E\u0439\u043D\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043E\u0442\u043C\u0435\u043D\u0451\u043D, TASWM-249). \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0435\u0433\u043E \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0441\u0430\u043C: ${readChecks}. \u0414\u0430\u043B\u044C\u0448\u0435 \u0442\u0440\u0438 \u0440\u0430\u0437\u043D\u044B\u0445 \u0441\u043B\u0443\u0447\u0430\u044F: \u2460 \u0445\u043E\u0442\u044C \u043E\u0434\u0438\u043D \u0447\u0435\u043A \u0435\u0449\u0451 PENDING \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u043E\u043E\u0431\u0449\u0435 \u043D\u0435 \u0432\u044B\u043D\u043E\u0441\u0438: \u043D\u0435 \u043F\u043E\u0441\u0442\u0438 REQUEST CHANGES, \u043D\u0435 \u0437\u043E\u0432\u0438 done(), \u043F\u0440\u043E\u0441\u0442\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E \u043A\u0430\u043A \u0435\u0441\u0442\u044C \u2014 \u0440\u043E\u0439 \u0441\u0430\u043C \u0437\u0430\u043C\u0435\u0442\u0438\u0442, \u0447\u0442\u043E \u0442\u044B \u0436\u0434\u0451\u0448\u044C CI, \u0438 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442 \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E, \u043A\u043E\u0433\u0434\u0430 \u0432\u0441\u0435 \u0447\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0442\u0441\u044F (\u044D\u0442\u043E \u0448\u0442\u0430\u0442\u043D\u044B\u0439 \u043F\u0443\u0442\u044C, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0437\u0432\u0430\u0442\u044C \u043D\u0435 \u043D\u0443\u0436\u043D\u043E). \u2461 \u0432\u0441\u0435 \u0447\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0438\u0441\u044C \u0438 \u0435\u0441\u0442\u044C FAIL \u2014 REQUEST CHANGES, \u043D\u0430\u0437\u043E\u0432\u0438 \u043F\u043E\u0438\u043C\u0451\u043D\u043D\u043E, \u0447\u0442\u043E \u0443\u043F\u0430\u043B\u043E. \u2462 \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u0447\u0435\u043A\u043E\u0432 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435 \u2014 \u0442\u043E\u0436\u0435 REQUEST CHANGES: \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C (\u0443 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430 verifyMode=ci, \u0442\u043E \u0435\u0441\u0442\u044C CI \u043E\u0431\u044F\u0437\u0430\u043D \u0431\u044B\u0442\u044C). ${dontTrustAuthor}`;
14713
15265
  }
15266
+ var REVIEW_DEFECT_CLASSES = [
15267
+ "\u041A\u043B\u0430\u0441\u0441\u044B \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u0432 \u2014 \u043F\u0440\u043E\u0439\u0434\u0438 \u043F\u043E \u043A\u0430\u0436\u0434\u043E\u043C\u0443 \u0438 \u0434\u0430\u0439 \u043E\u0442\u0432\u0435\u0442 \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435; \xAB\u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\xBB \u2014 \u0437\u0430\u043A\u043E\u043D\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442, \u043D\u043E \u043D\u0430\u0437\u0432\u0430\u043D\u043D\u044B\u0439 \u0438 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \u0432 \u043F\u043E\u043B\u0441\u0442\u0440\u043E\u043A\u0438 (\xAB\u0440\u0443\u0447\u0435\u043A \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u043E\xBB, \xAB\u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u0438\u0439 \u043D\u0435 \u0442\u0440\u043E\u043D\u0443\u0442\u043E\xBB). \u0428\u0435\u0441\u0442\u044C \u0440\u0430\u0437 \xAB\u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\xBB \u0431\u0435\u0437 \u043F\u0440\u0438\u0447\u0438\u043D \u2014 \u043D\u0435 \u0440\u0435\u0432\u044C\u044E, \u0430 \u043E\u0442\u043F\u0438\u0441\u043A\u0430:",
15268
+ "- [ ] \u041F\u0440\u0430\u0432\u0430 \u0430\u043A\u0442\u043E\u0440\u0430 \u0438 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441: \u043A\u0442\u043E \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043A\u0438 \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u0437\u0432\u0430\u0442\u044C \u043D\u043E\u0432\u0443\u044E \u0440\u0443\u0447\u043A\u0443 \u0438\u043B\u0438 \u043D\u0430\u0436\u0430\u0442\u044C \u043A\u043D\u043E\u043F\u043A\u0443 \u2014 \u0447\u0435\u043B\u043E\u0432\u0435\u043A, \u0434\u0440\u043E\u043D \u0440\u043E\u044F, \u0433\u043E\u0441\u0442\u044C, \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A \u0427\u0423\u0416\u041E\u0413\u041E \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430? \u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435, \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0432\u043E\u0434\u0438\u043B\u0430 \u0440\u0430\u0434\u0438 \u0420\u0415\u0428\u0415\u041D\u0418\u042F \u0427\u0415\u041B\u041E\u0412\u0415\u041A\u0410 (\u043F\u0443\u0431\u043B\u0438\u043A\u0430\u0446\u0438\u044F, \u0430\u043F\u0440\u0443\u0432, \u043E\u043F\u043B\u0430\u0442\u0430, \u0440\u0430\u0441\u0441\u044B\u043B\u043A\u0430), \u043E\u0431\u044F\u0437\u0430\u043D\u043E \u043E\u0442\u0431\u0438\u0432\u0430\u0442\u044C \u0430\u0433\u0435\u043D\u0442\u0430 \u044F\u0432\u043D\u043E (denyAgentActor \u0438 \u0430\u043D\u0430\u043B\u043E\u0433\u0438), \u0430 \u043D\u0435 \u043F\u043E\u043B\u0430\u0433\u0430\u0442\u044C\u0441\u044F \u043D\u0430 \u0442\u043E, \u0447\u0442\u043E \u0443 \u043D\u0435\u0433\u043E \u0440\u043E\u043B\u044C \u043F\u043E\u0436\u0438\u0436\u0435 \u2014 \u0443 \u0434\u0440\u043E\u043D\u0430 \u043E\u043D\u0430 \u043E\u0431\u044B\u0447\u043D\u043E \u043A\u0430\u043A \u0443 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430. \u041B\u044E\u0431\u043E\u0439 id, \u043F\u0440\u0438\u0435\u0445\u0430\u0432\u0448\u0438\u0439 \u0432 \u0442\u0435\u043B\u0435 \u0437\u0430\u043F\u0440\u043E\u0441\u0430, \u043E\u0431\u044F\u0437\u0430\u043D \u0441\u0432\u0435\u0440\u044F\u0442\u044C\u0441\u044F \u0441 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u043E\u043C \u0437\u0432\u043E\u043D\u044F\u0449\u0435\u0433\u043E: \u0440\u043E\u0443\u0442 \u0431\u0435\u0437 \u0442\u0430\u043A\u043E\u0439 \u0441\u0432\u0435\u0440\u043A\u0438 \u2014 \u043A\u0440\u043E\u0441\u0441-\u0442\u0435\u043D\u0430\u043D\u0442\u043D\u0430\u044F \u0434\u044B\u0440\u0430, \u0438 \u0432 \u044D\u0442\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u044D\u0442\u043E \u0441\u0430\u043C\u044B\u0439 \u0447\u0430\u0441\u0442\u044B\u0439 \u043A\u043B\u0430\u0441\u0441.",
15269
+ "- [ ] \u041F\u043E\u043B\u043D\u043E\u0442\u0430 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u043E\u0432: PR \u0434\u043E\u0431\u0430\u0432\u0438\u043B \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0432 enum/union/\u043D\u0430\u0431\u043E\u0440 \u0441\u0442\u0440\u043E\u043A \u2014 \u043D\u0430\u0439\u0434\u0438 \u0412\u0421\u0415 \u043C\u0435\u0441\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u044D\u0442\u0438 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u044F\u044E\u0442 (\u0431\u0435\u043B\u044B\u0435 \u0441\u043F\u0438\u0441\u043A\u0438, switch, \u043C\u0430\u043F\u043F\u0438\u043D\u0433\u0438, \u0443\u0441\u043B\u043E\u0432\u0438\u044F \u0432 UI), \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044C, \u0447\u0442\u043E \u043D\u043E\u0432\u044B\u0439 \u0442\u0430\u043C \u0435\u0441\u0442\u044C. typecheck \u044D\u0442\u043E \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442, \u043A\u043E\u0433\u0434\u0430 \u0441\u043F\u0438\u0441\u043E\u043A \u2014 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435, \u0430 \u043D\u0435 exhaustive-switch. \u042D\u0442\u043E \u041E\u0414\u0418\u041D grep \u043F\u043E \u0438\u043C\u0435\u043D\u0438 \u0442\u0438\u043F\u0430 \u0438\u043B\u0438 \u043F\u043E \u0441\u0430\u043C\u043E\u043C\u0443 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044E, \u0430 \u043D\u0435 \u0447\u0442\u0435\u043D\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \xAB\u0434\u043B\u044F \u043F\u043E\u043D\u0438\u043C\u0430\u043D\u0438\u044F\xBB \u2014 \u043A\u043E\u043D\u0442\u0443\u0440 \u044D\u043A\u043E\u043D\u043E\u043C\u0438\u0438 \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0440\u0435\u0449\u0430\u0435\u0442.",
15270
+ "- [ ] \u0412\u0441\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430: \u0443 \u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0438 \u043F\u043E\u0447\u0442\u0438 \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u0434\u0432\u0443\u0445 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0439 (\u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E, \u043F\u0430\u0443\u0437\u0430, drain, \u043C\u043E\u043B\u0447\u0438\u0442, \u0434\u0430\u043D\u043D\u044B\u0445 \u0435\u0449\u0451 \u043D\u0435\u0442, \u0434\u043E\u0441\u0442\u0443\u043F \u0437\u0430\u043A\u0440\u044B\u0442). \u0420\u0430\u0437\u043E\u0431\u0440\u0430\u043D\u044B \u0412\u0421\u0415 \u0438\u043B\u0438 \u0442\u043E\u043B\u044C\u043A\u043E happy path \u0438 \u043E\u0434\u043D\u0430 \u043E\u0448\u0438\u0431\u043A\u0430? \u0427\u0430\u0441\u0442\u043E \u043F\u043E\u043B\u043D\u044B\u0439 \u043D\u0430\u0431\u043E\u0440 \u0443\u0436\u0435 \u043B\u0435\u0436\u0438\u0442 \u0440\u044F\u0434\u043E\u043C \u0432 \u044F\u0434\u0440\u0435 \u2014 \u043F\u043E\u0438\u0449\u0438 \u0435\u0433\u043E, \u043F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u043D\u044B\u043C.",
15271
+ "- [ ] \u041E\u0448\u0438\u0431\u043A\u0430 \u0434\u043E\u0445\u043E\u0434\u0438\u0442 \u0434\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430: \u043E\u0442\u043A\u0430\u0437 \u0437\u0430\u043F\u0440\u043E\u0441\u0430, \u043F\u0440\u043E\u0432\u0430\u043B \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u0438, \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u2014 \u0432\u0438\u0434\u043D\u044B \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435, \u0430 \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 console.warn/\u043B\u043E\u0433. \u041A\u043D\u043E\u043F\u043A\u0430, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u043C\u043E\u043B\u0447\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u0434\u0435\u043B\u0430\u043B\u0430, \u2014 \u0434\u0435\u0444\u0435\u043A\u0442, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E.",
15272
+ "- [ ] \u0414\u0435\u0444\u043E\u043B\u0442 \u043F\u0440\u0438 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u0430\u043D\u043D\u044B\u0445: \u043F\u043E\u043B\u044F \u043D\u0435\u0442, \u043E\u0442\u0432\u0435\u0442 \u043F\u0443\u0441\u0442\u043E\u0439, \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \u043C\u043E\u043B\u0447\u0438\u0442 \u2014 \u043A\u0443\u0434\u0430 \u043F\u0430\u0434\u0430\u0435\u0442 \u0440\u0435\u0448\u0435\u043D\u0438\u0435? \u0414\u043B\u044F \u0443\u0434\u0430\u043B\u044F\u044E\u0449\u0438\u0445, \u043F\u0443\u0431\u043B\u0438\u043A\u0443\u044E\u0449\u0438\u0445 \u0438 \u0441\u043F\u0438\u0441\u044B\u0432\u0430\u044E\u0449\u0438\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u0435\u0444\u043E\u043B\u0442 \u043E\u0431\u044F\u0437\u0430\u043D \u0431\u044B\u0442\u044C \u0432 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u0443\u044E \u0441\u0442\u043E\u0440\u043E\u043D\u0443 (\u043D\u0435 \u0434\u0435\u043B\u0430\u0442\u044C), \u0430 \xAB\u043D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445\xBB \u043D\u0435 \u0441\u043C\u0435\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044C\u0441\u044F \u043A\u0430\u043A \xAB\u0447\u0438\u0441\u0442\u043E, \u043C\u043E\u0436\u043D\u043E\xBB.",
15273
+ "- [ ] \u0422\u0435\u0441\u0442\u044B PR \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u044E\u0442 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435. \u0415\u0441\u043B\u0438 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0423\u0416\u0415 \u043F\u043E\u0434\u043D\u044F\u0442\u043E (\u0432 worktree \u0435\u0441\u0442\u044C node_modules \u0438 \u043F\u0430\u043A\u0435\u0442\u044B \u0441\u043E\u0431\u0440\u0430\u043D\u044B): \u0441\u043D\u0438\u043C\u0438 \u0432 \u041A\u041E\u0414\u0415 \u0442\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043D\u043E\u0432\u044B\u0439 \u0442\u0435\u0441\u0442 \u044F\u043A\u043E\u0431\u044B \u0441\u0442\u043E\u0440\u043E\u0436\u0438\u0442 (\u043E\u0434\u043D\u0443 \u0441\u0442\u0440\u043E\u043A\u0443 \u2014 \u0433\u0435\u0439\u0442, \u0443\u0441\u043B\u043E\u0432\u0438\u0435, \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u044E), \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u041E\u0414\u0418\u041D \u044D\u0442\u043E\u0442 \u0442\u0435\u0441\u0442 (\u043D\u0435 \u0432\u0435\u0441\u044C \u0441\u044C\u044E\u0442), \u0432\u0435\u0440\u043D\u0438 \u0444\u0430\u0439\u043B \u043A\u0430\u043A \u0431\u044B\u043B\u043E. \u0422\u0435\u0441\u0442 \u043E\u0431\u044F\u0437\u0430\u043D \u043F\u043E\u043A\u0440\u0430\u0441\u043D\u0435\u0442\u044C; \u043E\u0441\u0442\u0430\u043B\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C \u2014 \u043D\u0430\u0445\u043E\u0434\u043A\u0430: \u043E\u043D \u043D\u0435 \u0441\u0442\u043E\u0440\u043E\u0436\u0438\u0442 \u043D\u0438\u0447\u0435\u0433\u043E.",
15274
+ " \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u041D\u0415 \u043F\u043E\u0434\u043D\u044F\u0442\u043E \u2014 \u0442\u0430\u043A \u0438 \u0431\u0443\u0434\u0435\u0442: \u0442\u0435\u0431\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0438 \u0447\u0438\u0441\u0442\u044B\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u0441\u0442\u0430\u0432\u0438\u043B. \u041F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C \u0438\u0445 \u041D\u0415 \u041D\u0410\u0414\u041E, \u044D\u0442\u043E \u0434\u043E\u0440\u043E\u0436\u0435 \u0432\u0441\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E: \u0441\u043A\u0430\u0436\u0438 \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435 \xAB\u043F\u0440\u043E\u0433\u043D\u0430\u0442\u044C \u043D\u0435 \u043C\u043E\u0433, \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442\xBB \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0442\u0435\u0441\u0442 \u0427\u0422\u0415\u041D\u0418\u0415\u041C. \u0422\u0440\u0438 \u0441\u043F\u043E\u0441\u043E\u0431\u0430 \u0441\u043E\u0432\u0440\u0430\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C, \u0432\u0438\u0434\u043D\u044B\u0435 \u0433\u043B\u0430\u0437\u0430\u043C\u0438: (\u0430) \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0433\u043B\u043E\u0447\u0435\u043D\u043E (`try/except`, \u0437\u043E\u043D\u0442 \u0441\u0442\u043E\u0440\u043E\u0436\u0430, `.catch(() => {})`) \u2014 \u0442\u0435\u0441\u0442 \u043D\u0435 \u043F\u0430\u0434\u0430\u0435\u0442 \u0442\u0430\u043C, \u0433\u0434\u0435 \u043E\u0431\u044F\u0437\u0430\u043D; (\u0431) \u0430\u0441\u0441\u0435\u0440\u0442 \u043D\u0430 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435, \u0432\u0435\u0440\u043D\u043E\u0435 \u0432\u0441\u0435\u0433\u0434\u0430, \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u043F\u0440\u0430\u0432\u043A\u0438; (\u0432) \u0442\u0435\u0441\u0442 \u043D\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u0438\u0437\u043C\u0435\u043D\u0451\u043D\u043D\u044B\u0439 \u0433\u0435\u0439\u0442 \u0432\u043E\u0432\u0441\u0435 \u2014 \u0434\u0451\u0440\u0433\u0430\u0435\u0442 \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0439 \u043F\u0443\u0442\u044C."
15275
+ ].join("\n");
14714
15276
  function buildReviewChecklist(reviewRun, files, priorReview, verifyMode = SWARM_VERIFY) {
14715
15277
  const header = files.length ? `\u0424\u0430\u0439\u043B\u044B \u0434\u0438\u0444\u0444\u0430 (${files.length}):
14716
15278
  ${files.map((f) => `- ${f}`).join("\n")}` : "\u0424\u0430\u0439\u043B\u044B \u0434\u0438\u0444\u0444\u0430: gh \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0441\u043F\u0438\u0441\u043E\u043A \u2014 \u0441\u043C. PR.";
@@ -14730,7 +15292,11 @@ ${priorReview}` : "",
14730
15292
  "\u041A\u043E\u0440\u043E\u0442\u043A\u0438\u0439 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0440\u0435-\u0447\u0435\u043A\u0430:",
14731
15293
  "- [ ] \u0417\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F \u043F\u0440\u043E\u0448\u043B\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u044B \u2014 \u043F\u043E \u043A\u0430\u0436\u0434\u043E\u043C\u0443 \u043F\u0443\u043D\u043A\u0442\u0443.",
14732
15294
  `- [ ] \u0420\u0435\u0433\u0440\u0435\u0441\u0441\u0430 \u043D\u0435\u0442: ${ciTrustLine(prUrl, verifyMode)}`,
14733
- "- [ ] \u041D\u043E\u0432\u044B\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u043D\u0435 \u043F\u0440\u0438\u0432\u043D\u0435\u0441\u043B\u0438 \u043D\u043E\u0432\u044B\u0445 \u043F\u0440\u043E\u0431\u043B\u0435\u043C (\u0433\u043E\u043D\u043A\u0438/\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438/UI \u043F\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\u0441\u0442\u0438)."
15295
+ "- [ ] \u041D\u043E\u0432\u044B\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u043D\u0435 \u043F\u0440\u0438\u0432\u043D\u0435\u0441\u043B\u0438 \u043D\u043E\u0432\u044B\u0445 \u043F\u0440\u043E\u0431\u043B\u0435\u043C: \u0433\u043E\u043D\u043A\u0438/\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438/UI \u043F\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\u0441\u0442\u0438 \u2014 \u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E \u043F\u0440\u0430\u0432\u0430 \u0430\u043A\u0442\u043E\u0440\u0430, \u0434\u0435\u0444\u043E\u043B\u0442 \u043F\u0440\u0438 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u0430\u043D\u043D\u044B\u0445, \u043F\u043E\u043B\u043D\u043E\u0442\u0430 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u043E\u0432. \u041F\u0440\u0430\u0432\u043A\u0430 \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443 \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u0442\u0435 \u0436\u0435 \u043C\u0435\u0441\u0442\u0430, \u0433\u0434\u0435 \u0434\u0435\u0444\u0435\u043A\u0442 \u0438 \u0431\u044B\u043B, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0438\u043C\u0435\u043D\u043D\u043E \u043E\u043D\u0430 \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043E \u043F\u0440\u0438\u043D\u043E\u0441\u0438\u0442 \u043D\u043E\u0432\u044B\u0439.",
15296
+ // Единственный класс из REVIEW_DEFECT_CLASSES, который на втором круге обязателен:
15297
+ // правки по вердикту почти всегда приносят НОВЫЕ тесты, и именно тут прошёл #819.
15298
+ // Остальные пять классов заново не гоняем — ре-чек намеренно короткий (TASWM-437).
15299
+ "- [ ] \u041D\u043E\u0432\u044B\u0435 \u0442\u0435\u0441\u0442\u044B \u043F\u0440\u0430\u0432\u043A\u0438: \u0441\u043D\u0438\u043C\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u0442\u0435\u0441\u0442 \u0441\u0442\u043E\u0440\u043E\u0436\u0438\u0442, \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u041E\u0414\u0418\u041D \u044D\u0442\u043E\u0442 \u0442\u0435\u0441\u0442, \u0432\u0435\u0440\u043D\u0438 \u043A\u0430\u043A \u0431\u044B\u043B\u043E \u2014 \u043E\u043D \u043E\u0431\u044F\u0437\u0430\u043D \u043F\u043E\u043A\u0440\u0430\u0441\u043D\u0435\u0442\u044C."
14734
15300
  ].filter(Boolean).join("\n");
14735
15301
  }
14736
15302
  return [
@@ -14740,8 +15306,11 @@ ${priorReview}` : "",
14740
15306
  "",
14741
15307
  verifyMode === "local" ? "\u0427\u0435\u043A-\u043B\u0438\u0441\u0442 (\u0433\u0435\u0439\u0442 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0433\u043E\u043D\u044F\u0435\u0448\u044C \u0442\u044B \u0441\u0430\u043C \u2014 \u0441\u043C. \u043F\u0435\u0440\u0432\u044B\u0439 \u043F\u0443\u043D\u043A\u0442; \u0434\u0430\u043B\u044C\u0448\u0435 \u0440\u0443\u043A\u0430\u043C\u0438 \u0442\u043E, \u0447\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u0435\u0442):" : "\u0427\u0435\u043A-\u043B\u0438\u0441\u0442 (\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0439 \u0440\u0443\u043A\u0430\u043C\u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u0447\u0435\u0433\u043E CI \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043A\u0438 \u043D\u0435 \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u2014 \u043D\u0435 \u0432\u0435\u0440\u044C \u0446\u0438\u0444\u0440\u0430\u043C \u0430\u0432\u0442\u043E\u0440\u0430 PR):",
14742
15308
  `- [ ] ${ciTrustLine(prUrl, verifyMode)}`,
15309
+ "",
15310
+ REVIEW_DEFECT_CLASSES,
15311
+ "",
14743
15312
  "- [ ] \u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 (\u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C) \u2014 \u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u043E\u0441\u0442\u044C \u0441\u043E \u0441\u0442\u0430\u0440\u044B\u043C\u0438 \u0434\u0430\u043D\u043D\u044B\u043C\u0438, \u043E\u0442\u043A\u0430\u0442.",
14744
- "- [ ] \u0413\u043E\u043D\u043A\u0438/\u043A\u043E\u043D\u043A\u0443\u0440\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C \u2014 race conditions, \u043F\u043E\u0440\u044F\u0434\u043E\u043A \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0439, \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C.",
15313
+ "- [ ] \u0413\u043E\u043D\u043A\u0438 \u2014 \u043F\u043E\u0440\u044F\u0434\u043E\u043A \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0439, \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E\u0433\u043E \u0432\u044B\u0437\u043E\u0432\u0430.",
14745
15314
  "- [ ] UI-\u0434\u0438\u0441\u0446\u0438\u043F\u043B\u0438\u043D\u0430 (\u0434\u043B\u044F web-PR) \u2014 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435 (uikit), \u0441\u043A\u0440\u0438\u043D\u044B mobile+desktop \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u044B \u041A \u0417\u0410\u0414\u0410\u0427\u0415 (done(screenshots), TASWM-294), \u0430 \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u043A PR.",
14746
15315
  "- [ ] \u0415\u0441\u043B\u0438 \u043F\u0440\u0430\u0432\u0438\u0442\u0441\u044F router.tsx \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430 per-page meta \u0434\u043B\u044F \u0430\u0433\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0430 (\u0438\u043D\u0430\u0447\u0435 NL-\u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F \u043C\u043E\u043B\u0447\u0430 \u043B\u043E\u043C\u0430\u0435\u0442\u0441\u044F)."
14747
15316
  ].join("\n");
@@ -14767,9 +15336,16 @@ var REVIEW_SCOPE_BLOCK = [
14767
15336
  "\u2022 \u0417\u0430\u0445\u043E\u0434\u043E\u0432 \u043D\u0430 \u043E\u0434\u0438\u043D \u0432\u043E\u043F\u0440\u043E\u0441 \u2014 \u043E\u0434\u0438\u043D. \u041D\u0435 \u043D\u0430\u0448\u0451\u043B \u043E\u0442\u0432\u0435\u0442\u0430 \u0441 \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u0440\u0430\u0437\u0430 (\u0438\u043B\u0438 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442 \u043C\u043E\u043B\u0447\u0438\u0442) \u2014 \u044D\u0442\u043E \u0443\u0436\u0435 \u043D\u0430\u0445\u043E\u0434\u043A\u0430: \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435, \u0430 \u043D\u0435 \u0443\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0439 \u0442\u0440\u0435\u0442\u0438\u0439 \u043A\u0440\u0443\u0433 \u043F\u043E\u0438\u0441\u043A\u0430.",
14768
15337
  "\u2022 \u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0441\u043F\u0435\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0412\u0421\u0415\u0413\u0414\u0410 \u043B\u0443\u0447\u0448\u0435 \u0438\u0434\u0435\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435 \u0431\u0443\u0434\u0435\u0442: \u043E\u0434\u043D\u043E\u0433\u043E \u043E\u0431\u043E\u0441\u043D\u043E\u0432\u0430\u043D\u043D\u043E\u0433\u043E \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F \u0445\u0432\u0430\u0442\u0430\u0435\u0442, \u0447\u0442\u043E\u0431\u044B PR \u043F\u043E\u0435\u0445\u0430\u043B \u043D\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0443. \u041A\u043E\u043F\u0430\u0435\u0448\u044C \u0434\u043E\u043B\u0433\u043E \u2014 \u0441\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0439\u0441\u044F \u0438 \u0432\u044B\u043D\u043E\u0441\u0438 \u0442\u043E, \u0447\u0442\u043E \u0435\u0441\u0442\u044C, \u0447\u0435\u0441\u0442\u043D\u043E \u043D\u0430\u0437\u0432\u0430\u0432, \u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u043C\u043E\u0442\u0440\u0435\u043B."
14769
15338
  ].join("\n");
15339
+ var REVIEW_MISSION_BLOCK = [
15340
+ "\u{1F3AF} \u0422\u0412\u041E\u042F \u0420\u0410\u0411\u041E\u0422\u0410 \u2014 \u041D\u0410\u0419\u0422\u0418 \u0414\u0415\u0424\u0415\u041A\u0422\u042B. \u041F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0435\u0442 PR, \u0440\u0430\u0431\u043E\u0442\u043E\u0439 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F: \u044D\u0442\u043E \u0430\u0432\u0442\u043E\u0440 \u0443\u0436\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043B \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438. \u0422\u044B \u0438\u0449\u0435\u0448\u044C, \u0447\u0442\u043E \u0441\u043B\u043E\u043C\u0430\u0435\u0442\u0441\u044F.",
15341
+ "\u2022 \u0417\u0435\u043B\u0451\u043D\u044B\u0439 CI \u043D\u0435 \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E \u0434\u0435\u0444\u0435\u043A\u0442\u0430 \u043D\u0435\u0442: \u043E\u043D \u0433\u043E\u043D\u044F\u0435\u0442 \u0442\u0435\u0441\u0442\u044B \u0430\u0432\u0442\u043E\u0440\u0430 \u0438 \u0442\u0438\u043F\u044B. \u0421\u043C\u043E\u0442\u0440\u0438 \u0440\u043E\u0432\u043D\u043E \u0442\u043E, \u0447\u0435\u0433\u043E \u043E\u043D \u043D\u0435 \u0432\u0438\u0434\u0438\u0442 \u2014 \u043F\u0440\u0430\u0432\u0430 \u0430\u043A\u0442\u043E\u0440\u0430, \u043C\u0443\u043B\u044C\u0442\u0438\u0442\u0435\u043D\u0430\u043D\u0442\u043D\u043E\u0441\u0442\u044C, \u0434\u0435\u0444\u043E\u043B\u0442\u044B \u043F\u0440\u0438 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u0430\u043D\u043D\u044B\u0445, \u043D\u0435\u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043D\u044B\u0435 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u0438 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F, \u0442\u0435\u043A\u0441\u0442 \u043E\u0448\u0438\u0431\u043A\u0438 \u0434\u043B\u044F \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435 \u043F\u0440\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438.",
15342
+ "\u2022 \u041D\u0430\u0445\u043E\u0434\u043A\u0430 \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u0421\u0426\u0415\u041D\u0410\u0420\u0418\u0415\u041C: \xAB\u0442\u0430\u043A\u043E\u0439 \u0432\u0445\u043E\u0434 \u0438\u043B\u0438 \u0442\u0430\u043A\u043E\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u2192 \u0432\u043E\u0442 \u0442\u0430\u043A\u043E\u0439 \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\xBB. \xAB\u0412\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u043D\u0435\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\xBB, \xAB\u0441\u0442\u043E\u0438\u0442 \u043F\u0435\u0440\u0435\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C\xBB, \xAB\u0436\u0435\u043B\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043F\u043E\u043A\u0440\u044B\u0442\u044C \u0442\u0435\u0441\u0442\u0430\u043C\u0438\xBB \u2014 \u043D\u0435 \u043D\u0430\u0445\u043E\u0434\u043A\u0438; \u0442\u0430\u043A\u043E\u0435 \u043D\u0435 \u043F\u0438\u0448\u0438.",
15343
+ "\u2022 \u041D\u043E\u043B\u044C \u043D\u0430\u0445\u043E\u0434\u043E\u043A \u2014 \u0437\u0430\u043A\u043E\u043D\u043D\u044B\u0439 \u0432\u0435\u0440\u0434\u0438\u043A\u0442, \u043D\u043E \u0442\u043E\u0433\u0434\u0430 \u043D\u0430\u0437\u043E\u0432\u0438, \u0427\u0422\u041E \u0438\u043C\u0435\u043D\u043D\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u043A\u0430\u043A\u0438\u043C \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0435\u043C. APPROVE \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0440\u043E\u0439 \u0447\u0438\u0442\u0430\u0435\u0442 \u043A\u0430\u043A \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437, \u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 PR \u0437\u0430\u043D\u043E\u0432\u043E \u0432\u0440\u0443\u0447\u043D\u0443\u044E."
15344
+ ].join("\n");
14770
15345
  function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachmentsBlock, verifyMode = SWARM_VERIFY) {
14771
15346
  const name = reviewer.name || `#${reviewer.id}`;
14772
15347
  const prUrl = reviewRun.pr_url ?? "(\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR \u2014 \u0441\u043C. \u043B\u043E\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430)";
15348
+ const classesStep = reviewRun.review_cycle > 0 ? "" : "\u043F\u0440\u043E\u0439\u0434\u0438 \u043A\u043B\u0430\u0441\u0441\u044B \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u0432 (\u043F\u043E \u043A\u0430\u0436\u0434\u043E\u043C\u0443 \u2014 \u043E\u0442\u0432\u0435\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439, \xAB\u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\xBB \u0442\u043E\u0436\u0435), ";
14773
15349
  return [
14774
15350
  `\u0422\u044B \u2014 \u0434\u0440\u043E\u043D-\u0420\u0415\u0412\u042C\u042E\u0415\u0420 ${name} \u0440\u043E\u044F, headless-\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C (\u041D\u0415 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u0447\u0430\u0442). \u0410\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E (TASWM-148): \u0440\u043E\u0439 \u0441\u0430\u043C \u043F\u043E\u0434\u043D\u044F\u043B \u0442\u0435\u0431\u044F \u043D\u0430 PR \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F \u2014 \u0442\u044B \u041D\u0415 \u0432\u044B\u0431\u0440\u0430\u043D \u0438\u043C \u0438 \u043E\u0442 \u043D\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0448\u044C.`,
14775
15351
  "",
@@ -14783,7 +15359,12 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
14783
15359
  `\u26A0\uFE0F \u042D\u0442\u043E\u0442 PR \u043E\u0442\u043A\u0440\u044B\u0442 \u0432 \u0432\u0435\u0442\u043A\u0443 \`${reviewRun.base_branch}\` (\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u0430\u044F \u043F\u0430\u0447\u043A\u0430), \u0430 \u041D\u0415 \u0432 \u0432\u0435\u0442\u043A\u0443 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E. \u0414\u0438\u0444 \u0441\u043C\u043E\u0442\u0440\u0438 \u0441\u0442\u0440\u043E\u0433\u043E \u043E\u0442 \u043D\u0435\u0451: \`git diff origin/${reviewRun.base_branch}...HEAD\` (\u0441\u043F\u0438\u0441\u043E\u043A \u0444\u0430\u0439\u043B\u043E\u0432 \u043D\u0438\u0436\u0435 \u0443\u0436\u0435 \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u043D gh \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u044D\u0442\u043E\u0439 \u0431\u0430\u0437\u044B). \u0421\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u0435 \u0441 main \u043F\u043E\u043A\u0430\u0437\u0430\u043B\u043E \u0431\u044B \u0442\u0435\u0431\u0435 \u0447\u0443\u0436\u0438\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043F\u0430\u0447\u043A\u0438 \u043A\u0430\u043A \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u044D\u0442\u043E\u0433\u043E \u0434\u0440\u043E\u043D\u0430 \u2014 \u043D\u0435 \u0434\u0435\u043B\u0430\u0439 \u0442\u0430\u043A.`
14784
15360
  ] : [],
14785
15361
  "",
14786
- "\u26A0\uFE0F \u0422\u042B \u041D\u0415 \u041F\u0420\u0410\u0412\u0418\u0428\u042C \u0424\u0410\u0419\u041B\u042B \u0418 \u041D\u0415 \u041F\u0423\u0428\u0418\u0428\u042C \u041A\u041E\u041C\u041C\u0418\u0422\u042B. \u0422\u0432\u043E\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0438 \u0432\u044B\u043D\u0435\u0441\u0442\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442.",
15362
+ // TASWM-437: мутационная проверка теста — это правка файла, и без явного исключения
15363
+ // ревьюер честно откажется её делать. Разрешаем ровно её: локально, с возвратом файла,
15364
+ // без коммита и пуша (worktree ревьюера всё равно удаляется после прогона).
15365
+ "\u26A0\uFE0F \u0422\u042B \u041D\u0415 \u041F\u0420\u0410\u0412\u0418\u0428\u042C \u041A\u041E\u0414 PR \u0418 \u041D\u0415 \u041F\u0423\u0428\u0418\u0428\u042C \u041A\u041E\u041C\u041C\u0418\u0422\u042B. \u0422\u0432\u043E\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0438 \u0432\u044B\u043D\u0435\u0441\u0442\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442. \u0415\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u0430\u044F \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D\u043D\u0430\u044F \u043F\u0440\u0430\u0432\u043A\u0430 \u2014 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u043C\u0443\u0442\u0430\u0446\u0438\u044F \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0442\u0435\u0441\u0442\u0430 (\u043F\u0443\u043D\u043A\u0442 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442\u0430 \u043F\u0440\u043E \u0442\u0435\u0441\u0442\u044B): \u0441\u043D\u044F\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443, \u043F\u0440\u043E\u0433\u043D\u0430\u043B \u043E\u0434\u0438\u043D \u0442\u0435\u0441\u0442, \u0432\u0435\u0440\u043D\u0443\u043B \u0444\u0430\u0439\u043B (`git checkout -- <\u0444\u0430\u0439\u043B>`). \u0412\u043E\u0437\u0432\u0440\u0430\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u0414\u041E \u043A\u043E\u043D\u0446\u0430 \u0441\u0435\u0441\u0441\u0438\u0438, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u0441\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0448\u044C\u0441\u044F \u0434\u043E\u0441\u0440\u043E\u0447\u043D\u043E (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \u0443\u0445\u043E\u0434\u0438\u0448\u044C \u0436\u0434\u0430\u0442\u044C CI): `git status` \u0432 worktree \u043E\u0431\u044F\u0437\u0430\u043D \u0431\u044B\u0442\u044C \u0447\u0438\u0441\u0442. \u041A\u043E\u043C\u043C\u0438\u0442\u0438\u0442\u044C, \u043F\u0443\u0448\u0438\u0442\u044C \u0438 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0442\u044C \u0441\u0432\u043E\u0438 PR \u043D\u0435\u043B\u044C\u0437\u044F \u043D\u0438\u0447\u0435\u0433\u043E \u0438 \u043D\u0438\u043A\u043E\u0433\u0434\u0430.",
15366
+ "",
15367
+ REVIEW_MISSION_BLOCK,
14787
15368
  "",
14788
15369
  REVIEW_SCOPE_BLOCK,
14789
15370
  // TASWM-283: ревьюер читает ТУ ЖЕ задачу и так же слеп к её картинкам — без макета
@@ -14795,11 +15376,11 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
14795
15376
  "",
14796
15377
  "\u041F\u043E\u0440\u044F\u0434\u043E\u043A:",
14797
15378
  ...verifyMode === "local" ? [
14798
- "1. \u041F\u0440\u043E\u0439\u0434\u0438 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0432\u044B\u0448\u0435: \u0433\u0435\u0439\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u0421\u0410\u041C \u0432 \u0441\u0432\u043E\u0451\u043C worktree (\u044D\u0442\u043E \u0438 \u0435\u0441\u0442\u044C \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u0437\u0435\u043B\u0451\u043D\u043E\u0433\u043E \u2014 \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 verifyMode=local), \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438/\u0433\u043E\u043D\u043A\u0438/UI/per-page meta \u043F\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\u0441\u0442\u0438.",
15379
+ `1. \u041F\u0440\u043E\u0439\u0434\u0438 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0432\u044B\u0448\u0435: \u0433\u0435\u0439\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u0421\u0410\u041C \u0432 \u0441\u0432\u043E\u0451\u043C worktree (\u044D\u0442\u043E \u0438 \u0435\u0441\u0442\u044C \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u0437\u0435\u043B\u0451\u043D\u043E\u0433\u043E \u2014 \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 verifyMode=local), ${classesStep}\u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438/\u0433\u043E\u043D\u043A\u0438/UI/per-page meta \u043F\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\u0441\u0442\u0438.`,
14799
15380
  " \u0427\u0435\u043A\u043E\u0432 \u043D\u0430 \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u043D\u0435\u0442 (\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435\u0442 CI) \u2014 \u044D\u0442\u043E \u041D\u0415 \u0434\u0435\u0444\u0435\u043A\u0442 PR \u0438 \u041D\u0415 \u043F\u043E\u0432\u043E\u0434 \u0434\u043B\u044F REQUEST CHANGES: \u043D\u0435 \u0436\u0434\u0438 \u0438\u0445, \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0439 \u0437\u0430\u0432\u0435\u0441\u0442\u0438 workflow, \u0441\u0443\u0434\u0438 \u043F\u043E \u0441\u0432\u043E\u0435\u043C\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0443. \u041A\u0440\u0430\u0441\u043D\u044B\u0439 \u0417\u0410\u0412\u0415\u0420\u0428\u0418\u0412\u0428\u0418\u0419\u0421\u042F \u0447\u0435\u043A, \u0435\u0441\u043B\u0438 CI \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0432\u0441\u0451-\u0442\u0430\u043A\u0438 \u0435\u0441\u0442\u044C, \u2014 \u043F\u043E-\u043F\u0440\u0435\u0436\u043D\u0435\u043C\u0443 \u0431\u043B\u043E\u043A.",
14800
15381
  `2. \u041F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u043E\u043D\u0447\u0435\u043D \u2014 \u043E\u0441\u0442\u0430\u0432\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u043D\u0430 \u0441\u0430\u043C PR: \`gh pr comment ${prUrl} --body "..."\` (\u041D\u0415 \`gh pr review\` \u2014 GitHub \u0435\u0433\u043E \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442 \u043D\u0430 \u0441\u0432\u043E\u0451\u043C PR). \u041D\u0430\u0447\u043D\u0438 \u0441 **APPROVE** \u0438\u043B\u0438 **REQUEST CHANGES** \u0437\u0430\u0433\u043B\u0430\u0432\u043D\u044B\u043C\u0438, \u0434\u0430\u043B\u044C\u0448\u0435 \u2014 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0438\u043A\u0430: \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B (\u043F\u0440\u0438\u0432\u0435\u0434\u0438 \u0432\u044B\u0432\u043E\u0434 \u0441\u0432\u043E\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430), \u0447\u0442\u043E \u043D\u0430\u0448\u0451\u043B; \u0434\u043B\u044F REQUEST CHANGES \u2014 \u0442\u043E\u0447\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B/\u0441\u0442\u0440\u043E\u043A\u0438 \u0438 \u0447\u0442\u043E \u0447\u0438\u043D\u0438\u0442\u044C. \u041F\u0440\u0435\u0442\u0435\u043D\u0437\u0438\u044F \u043A \u0438\u043D\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F (\u043D\u0435\u0442 CI, \u043A\u043E\u043D\u0444\u0438\u0433\u0438, \u0434\u0435\u043F\u043B\u043E\u0439) \u2014 \u043D\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E PR: \u0441\u0447\u0438\u0442\u0430\u0435\u0448\u044C, \u0447\u0442\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E \u043D\u0443\u0436\u0435\u043D CI, \u2014 \u0441\u043A\u0430\u0436\u0438 \u044D\u0442\u043E \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \u0432 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0435, \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043E\u0442 \u043D\u0435\u0451 \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442.`
14801
15382
  ] : [
14802
- "1. \u041F\u0440\u043E\u0439\u0434\u0438 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0432\u044B\u0448\u0435: \u0441\u0442\u0430\u0442\u0443\u0441 CI \u043F\u043E \u044D\u0442\u043E\u043C\u0443 \u043A\u043E\u043C\u043C\u0438\u0442\u0443 \u0447\u0438\u0442\u0430\u0439 \u0441\u0430\u043C (\u043D\u0435 \u0432\u0435\u0440\u044C \u043E\u0442\u0447\u0451\u0442\u0443 \u0430\u0432\u0442\u043E\u0440\u0430 PR, \u043D\u043E \u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0439 CI \u0432\u0440\u0443\u0447\u043D\u0443\u044E), \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438/\u0433\u043E\u043D\u043A\u0438/UI/per-page meta \u043F\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\u0441\u0442\u0438.",
15383
+ `1. \u041F\u0440\u043E\u0439\u0434\u0438 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0432\u044B\u0448\u0435: \u0441\u0442\u0430\u0442\u0443\u0441 CI \u043F\u043E \u044D\u0442\u043E\u043C\u0443 \u043A\u043E\u043C\u043C\u0438\u0442\u0443 \u0447\u0438\u0442\u0430\u0439 \u0441\u0430\u043C (\u043D\u0435 \u0432\u0435\u0440\u044C \u043E\u0442\u0447\u0451\u0442\u0443 \u0430\u0432\u0442\u043E\u0440\u0430 PR, \u043D\u043E \u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0439 CI \u0432\u0440\u0443\u0447\u043D\u0443\u044E), ${classesStep}\u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438/\u0433\u043E\u043D\u043A\u0438/UI/per-page meta \u043F\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\u0441\u0442\u0438.`,
14803
15384
  " \u0415\u0441\u043B\u0438 \u0445\u043E\u0442\u044C \u043E\u0434\u0438\u043D \u0447\u0435\u043A \u0435\u0449\u0451 PENDING \u2014 \u043D\u0430 \u044D\u0442\u043E\u043C \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0441\u044C: \u0448\u0430\u0433\u0438 2 \u0438 3 \u043D\u0438\u0436\u0435 \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438 \u0446\u0435\u043B\u0438\u043A\u043E\u043C, \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043D\u0435 \u0432\u044B\u043D\u043E\u0441\u0438, \u043F\u0440\u043E\u0441\u0442\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E (\u0441\u043C. \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0432\u044B\u0448\u0435, \u043F\u043E\u0447\u0435\u043C\u0443 \u044D\u0442\u043E \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E \u0438 \u0447\u0442\u043E \u043F\u0440\u043E\u0438\u0437\u043E\u0439\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435).",
14804
15385
  `2. \u0427\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0438\u0441\u044C (\u0437\u0435\u043B\u0451\u043D\u044B\u0435 \u0438\u043B\u0438 \u0441 \u043A\u0440\u0430\u0441\u043D\u044B\u043C) \u0438\u043B\u0438 \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u0438\u0445 \u0432\u043E\u0432\u0441\u0435 \u043D\u0435\u0442 \u2014 \u043E\u0441\u0442\u0430\u0432\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u043D\u0430 \u0441\u0430\u043C PR: \`gh pr comment ${prUrl} --body "..."\` (\u041D\u0415 \`gh pr review\` \u2014 GitHub \u0435\u0433\u043E \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442 \u043D\u0430 \u0441\u0432\u043E\u0451\u043C PR). \u041D\u0430\u0447\u043D\u0438 \u0441 **APPROVE** \u0438\u043B\u0438 **REQUEST CHANGES** \u0437\u0430\u0433\u043B\u0430\u0432\u043D\u044B\u043C\u0438, \u0434\u0430\u043B\u044C\u0448\u0435 \u2014 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0438\u043A\u0430: \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B, \u0447\u0442\u043E \u043D\u0430\u0448\u0451\u043B; \u0434\u043B\u044F REQUEST CHANGES \u2014 \u0442\u043E\u0447\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B/\u0441\u0442\u0440\u043E\u043A\u0438 \u0438 \u0447\u0442\u043E \u0447\u0438\u043D\u0438\u0442\u044C.`
14805
15386
  ],
@@ -14836,6 +15417,7 @@ async function manageStorm(deps, now, worktreeExists, hasTranscript) {
14836
15417
  const consec = trailingProbeSuccesses(recs);
14837
15418
  if (stormShouldEnd({ consecutiveProbeOk: consec, externalDown: lastExternalDown(recs) === true }, cfg)) {
14838
15419
  db.endStorm(`${STORM_END_TEXT} (${consec} \u0443\u0441\u043F\u0435\u0448\u043D\u044B\u0435 health-probe \u043F\u043E\u0434\u0440\u044F\u0434)`);
15420
+ if (deps.spawnDefer) clearDefer(deps.spawnDefer, "storm");
14839
15421
  narrate(deps, "\u0428\u0442\u043E\u0440\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u0441\u044F \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0437\u0430\u043C\u043E\u0440\u043E\u0436\u0435\u043D\u043D\u044B\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B.");
14840
15422
  await resurrectStormDead(deps, worktreeExists, hasTranscript);
14841
15423
  return false;
@@ -15117,8 +15699,10 @@ async function wakeReviewerOnCiReady(deps, run, over) {
15117
15699
  if (stats.count >= CI_WAKE_LIMIT || !canContinue) {
15118
15700
  const why = !canContinue ? "worktree/\u0441\u0435\u0441\u0441\u0438\u044F \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u2014 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" : `${CI_WAKE_LIMIT} \u043F\u043E\u0431\u0443\u0434(\u043E\u043A) \u043F\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 CI \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E`;
15119
15701
  deps.db.updateRun(reviewer.id, { status: "stalled" });
15120
- deps.db.addEvent(reviewer.id, "stalled", `CI \u0433\u043E\u0442\u043E\u0432, \u043D\u043E \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0432\u044B\u0448\u043B\u043E: ${why} \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 PR`);
15121
- narrate(deps, `${droneLabel(reviewer)} \u0436\u0434\u0430\u043B CI, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${why}) \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430, \u043D\u0435 \u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E \u0432 stalled.`);
15702
+ const freshWanted = await projectWantsReviewer(deps, run);
15703
+ const freshPlan = freshWanted ? "\u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 PR" : "\u0441\u0432\u0435\u0436\u0435\u0433\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E: \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E";
15704
+ deps.db.addEvent(reviewer.id, "stalled", `CI \u0433\u043E\u0442\u043E\u0432, \u043D\u043E \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0432\u044B\u0448\u043B\u043E: ${why} \u2014 ${freshPlan}`);
15705
+ narrate(deps, `${droneLabel(reviewer)} \u0436\u0434\u0430\u043B CI, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${why}) \u2014 ${freshPlan}.`);
15122
15706
  if (deps.watchdog?.notify) {
15123
15707
  const signal = {
15124
15708
  key: `reviewer_ci_wake_failed:${reviewer.id}`,
@@ -15127,7 +15711,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
15127
15711
  // поднимает свежего ревьюера на том же PR. Решать человеку тут нечего; если и
15128
15712
  // свежий не доедет, конвейер встанет и об этом скажет уже pr_stuck_* пушем.
15129
15713
  level: "feed",
15130
- body: `\u0420\u0435\u0432\u044C\u044E\u0435\u0440 ${droneLabel(reviewer)} \u0434\u043E\u0436\u0434\u0430\u043B\u0441\u044F \u0433\u043E\u0442\u043E\u0432\u043E\u0433\u043E CI, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${why}). \u041F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 PR \u2014 ${run.pr_url ?? "?"}.`,
15714
+ body: `\u0420\u0435\u0432\u044C\u044E\u0435\u0440 ${droneLabel(reviewer)} \u0434\u043E\u0436\u0434\u0430\u043B\u0441\u044F \u0433\u043E\u0442\u043E\u0432\u043E\u0433\u043E CI, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${why}). ${freshWanted ? "\u041F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 PR" : "\u0421\u0432\u0435\u0436\u0435\u0433\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E (\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E)"} \u2014 ${run.pr_url ?? "?"}.`,
15131
15715
  runId: reviewer.id,
15132
15716
  ...hrefOf(run.task_url)
15133
15717
  };
@@ -15137,7 +15721,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
15137
15721
  console.error(`[overmind] \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0435 \u043E \u043F\u043E\u0442\u0435\u0440\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 #${reviewer.id} \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u043E:`, e);
15138
15722
  }
15139
15723
  }
15140
- await spawnFreshReviewerAfterStall(deps, run, why);
15724
+ if (freshWanted) await spawnFreshReviewerAfterStall(deps, run, why);
15141
15725
  return true;
15142
15726
  }
15143
15727
  try {
@@ -15159,6 +15743,10 @@ function worktreeAndTranscriptAlive(deps, worktree) {
15159
15743
  const transcript = deps.hasTranscript ?? executorHasSession;
15160
15744
  return exists(worktree) && transcript(worktree);
15161
15745
  }
15746
+ async function projectWantsReviewer(deps, run) {
15747
+ if (!deps.autoReviewForRepo) return true;
15748
+ return await deps.autoReviewForRepo(effectiveRepo(deps, run)).catch(() => true);
15749
+ }
15162
15750
  async function spawnFreshReviewerAfterStall(deps, run, why) {
15163
15751
  if (!deps.spawnReviewer) return;
15164
15752
  const live = liveOrWaitingReviewers(deps.db, run.id);
@@ -15251,23 +15839,23 @@ async function freshChangesWakeAllowed(deps, run, prUrl, now) {
15251
15839
  const moved = await deps.commitsSince(prUrl, verdictAt ?? run.created_at).catch(() => true);
15252
15840
  return !moved;
15253
15841
  }
15254
- async function prodStillAllows(deps, run, allowed) {
15255
- if (!deps.db.refreshRun) return true;
15842
+ async function prodOverrideReason(deps, run, allowed) {
15843
+ if (!deps.db.refreshRun) return null;
15256
15844
  let fresh;
15257
15845
  try {
15258
15846
  fresh = await deps.db.refreshRun(run.id);
15259
15847
  } catch {
15260
- return true;
15848
+ return null;
15261
15849
  }
15262
- if (!fresh) return false;
15263
- if (allowed.includes(fresh.status)) return true;
15850
+ if (!fresh) return "\u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442 \u0432 \u043C\u043E\u0451\u043C \u0437\u0435\u0440\u043A\u0430\u043B\u0435 \u2014 \u043F\u0440\u043E\u0434 \u043E\u0442\u0434\u0430\u043B \u0435\u0433\u043E \u0434\u0440\u0443\u0433\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 (TASWM-300)";
15851
+ if (allowed.includes(fresh.status)) return null;
15264
15852
  deps.db.addEvent(
15265
15853
  run.id,
15266
15854
  "step",
15267
15855
  `\u043F\u0440\u043E\u0434 \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \xAB${fresh.status}\xBB (\u0443 \u043C\u0435\u043D\u044F \u0431\u044B\u043B\u043E \xAB${run.status}\xBB) \u2014 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E, \u0441\u0442\u0430\u0442\u0443\u0441 \u043F\u0440\u043E\u0434\u0430 \u0441\u0438\u043B\u044C\u043D\u0435\u0435 (TASWM-300)`
15268
15856
  );
15269
15857
  narrate(deps, `${droneLabel(run)} \u2014 \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0443\u0436\u0435 \xAB${fresh.status}\xBB, \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E: \u043F\u0440\u043E\u0434 \u0441\u0438\u043B\u044C\u043D\u0435\u0435 \u043C\u043E\u0435\u0433\u043E \u0437\u0435\u0440\u043A\u0430\u043B\u0430.`);
15270
- return false;
15858
+ return `\u043F\u0440\u043E\u0434 \u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043B \u043F\u0440\u043E\u0433\u043E\u043D, \u043F\u043E\u043A\u0430 \u0442\u043E\u0442 \u0436\u0434\u0430\u043B: \u0442\u0430\u043C \u0443\u0436\u0435 \xAB${fresh.status}\xBB, \u0443 \u043C\u0435\u043D\u044F \u0431\u044B\u043B\u043E \xAB${run.status}\xBB (TASWM-300)`;
15271
15859
  }
15272
15860
  async function envGateAllows(deps, run) {
15273
15861
  if (!deps.envGate) return true;
@@ -15373,13 +15961,18 @@ async function projectQuotaGateAllows(deps, run) {
15373
15961
  return true;
15374
15962
  }
15375
15963
  function pruneGateLogs(deps) {
15376
- if (!deps.envGateLogged?.size && !deps.exclusiveLogged?.size && !deps.overlapLogged?.size) return;
15964
+ if (!deps.envGateLogged?.size && !deps.exclusiveLogged?.size && !deps.overlapLogged?.size && !deps.spawnDefer?.size) return;
15377
15965
  const open = /* @__PURE__ */ new Set();
15378
15966
  for (const run of deps.db.listRuns()) if (!isTerminalStatus(run.status)) open.add(run.id);
15379
15967
  for (const map of [deps.envGateLogged, deps.exclusiveLogged, deps.overlapLogged]) {
15380
15968
  if (!map) continue;
15381
15969
  for (const id of [...map.keys()]) if (!open.has(id)) map.delete(id);
15382
15970
  }
15971
+ if (deps.spawnDefer) {
15972
+ const live = /* @__PURE__ */ new Set(["storm", "control-pause"]);
15973
+ for (const id of open) live.add(`run:${id}`);
15974
+ pruneDefers(deps.spawnDefer, live);
15975
+ }
15383
15976
  }
15384
15977
  async function projectQuotaBlockedRunIds(deps) {
15385
15978
  const blocked = /* @__PURE__ */ new Set();
@@ -15417,8 +16010,38 @@ function noteExhaustedDeps(deps, run, verdicts) {
15417
16010
  narrate(deps, `${droneLabel(run)} \u2014 ${text}`);
15418
16011
  }
15419
16012
  }
16013
+ function deferSpawn(deps, run, reason, opts = {}) {
16014
+ const eps = deps.spawnDefer ??= /* @__PURE__ */ new Map();
16015
+ const d = noteDefer(eps, `run:${run.id}`, reason, GATE_LOG_HEARTBEAT_TICKS);
16016
+ if (d.changed && opts.event !== false) deps.db.addEvent(run.id, "step", `\u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D: ${reason} \u2014 \u0436\u0434\u0443 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 (TASWM-333)`);
16017
+ const shout = opts.logKey ? noteDefer(eps, opts.logKey, reason, GATE_LOG_HEARTBEAT_TICKS) : d;
16018
+ if (shout.log && !opts.quiet) {
16019
+ const suffix = shout.changed ? "" : ` \u2014 \u0435\u0449\u0451 \u0432\u0438\u0441\u0438\u0442, \u0442\u0438\u043A ${shout.ticks}`;
16020
+ const who = opts.logKey ? "" : `${droneLabel(run)} (${run.task_slug}) \u2014 `;
16021
+ console.error(`[overmind] \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D: ${who}${reason}${suffix}`);
16022
+ }
16023
+ return "skipped";
16024
+ }
16025
+ function noteStormStall(deps) {
16026
+ const waiting = deps.db.listRuns("queued").filter((r) => deps.db.unmetDeps(r.depends_on).length === 0);
16027
+ if (waiting.length === 0) return;
16028
+ const startedAt = deps.db.currentStormStartedAt();
16029
+ const reason = `\u0440\u043E\u0439 \u0432 \u0448\u0442\u043E\u0440\u043C\u043E\u0432\u043E\u043C \u0440\u0435\u0436\u0438\u043C\u0435${startedAt ? ` \u0441 ${startedAt} UTC` : ""} \u2014 \u043D\u043E\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C \u0434\u043E \u043E\u0442\u0431\u043E\u044F (TASWM-23)`;
16030
+ const d = noteDefer(deps.spawnDefer ??= /* @__PURE__ */ new Map(), "storm", reason, GATE_LOG_HEARTBEAT_TICKS);
16031
+ if (d.log) {
16032
+ const suffix = d.changed ? "" : ` \u2014 \u0435\u0449\u0451 \u0432\u0438\u0441\u0438\u0442, \u0442\u0438\u043A ${d.ticks}`;
16033
+ console.error(`[overmind] \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D: ${reason}; \u0436\u0434\u0443\u0442 ${waiting.length} \u043F\u0440\u043E\u0433\u043E\u043D(\u043E\u0432)${suffix}`);
16034
+ }
16035
+ for (const run of waiting) deferSpawn(deps, run, reason, { quiet: true });
16036
+ }
15420
16037
  async function trySpawnQueuedRun(deps, run) {
15421
- if (deps.control?.paused) return "skipped";
16038
+ if (deps.control?.paused) {
16039
+ const why = deps.control.state?.reason;
16040
+ return deferSpawn(deps, run, `\u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B \u043F\u0440\u0438\u0451\u043C \u043D\u043E\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435${why ? ` \u2014 ${why}` : ""} (\u043F\u0430\u0443\u0437\u0430)`, {
16041
+ logKey: "control-pause"
16042
+ // причина одна на машину — в лог уходит одной строкой, а не по строке на прогон
16043
+ });
16044
+ }
15422
16045
  const mergedUrl = doneMergedPrForSlug(deps.db, run.task_slug) ?? (deps.mergedPrForBranch ? await deps.mergedPrForBranch(run.task_slug, run.repo || "").catch(() => null) : null);
15423
16046
  if (mergedUrl) {
15424
16047
  await closeDuplicateTaskRun(deps, run, mergedUrl);
@@ -15428,15 +16051,20 @@ async function trySpawnQueuedRun(deps, run) {
15428
16051
  return "closed";
15429
16052
  }
15430
16053
  const depVerdicts = deps.db.depVerdicts(run.depends_on);
15431
- if (depVerdicts.some((d) => !d.met)) return "skipped";
16054
+ const unmet = depVerdicts.filter((d) => !d.met);
16055
+ if (unmet.length) return deferSpawn(deps, run, `\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438: ${unmet.map((d) => d.slug).join(", ")}`);
15432
16056
  if (!await envGateAllows(deps, run)) return "skipped";
15433
16057
  if (!await projectQuotaGateAllows(deps, run)) return "skipped";
15434
- if (!await prodStillAllows(deps, run, ["queued"])) return "skipped";
15435
- if (deps.db.takeRun && !await deps.db.takeRun(run.id)) return "skipped";
16058
+ const prodOverride = await prodOverrideReason(deps, run, ["queued"]);
16059
+ if (prodOverride) return deferSpawn(deps, run, prodOverride, { event: false });
16060
+ if (deps.db.takeRun && !await deps.db.takeRun(run.id)) {
16061
+ return deferSpawn(deps, run, "\u043D\u0435 \u0432\u0437\u044F\u043B \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0430 \u0441\u0432\u043E\u044E \u043C\u0430\u0448\u0438\u043D\u0443 \u2014 \u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u0434\u0430\u043B \u0435\u0433\u043E \u0434\u0440\u0443\u0433\u043E\u043C\u0443 \u0434\u0435\u043C\u043E\u043D\u0443 (TASWM-281)");
16062
+ }
15436
16063
  noteExhaustedDeps(deps, run, depVerdicts);
15437
16064
  await fillSuggestedArea(deps, run);
15438
16065
  try {
15439
16066
  await deps.spawn(run);
16067
+ if (deps.spawnDefer) clearDefer(deps.spawnDefer, `run:${run.id}`);
15440
16068
  narrate(deps, `\u0411\u0435\u0440\u0443 ${run.task_slug} \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u2014 \u0441\u043F\u0430\u0432\u043D\u044E ${droneLabel(run)}.`);
15441
16069
  return "spawned";
15442
16070
  } catch (e) {
@@ -15449,7 +16077,7 @@ async function trySpawnQueuedRun(deps, run) {
15449
16077
  }
15450
16078
  return "skipped";
15451
16079
  }
15452
- if (e instanceof AreaBusyError) return "skipped";
16080
+ if (e instanceof AreaBusyError) return deferSpawn(deps, run, e.message);
15453
16081
  deps.db.updateRun(run.id, { status: "failed" });
15454
16082
  deps.db.addEvent(run.id, "failed", String(e));
15455
16083
  return "failed";
@@ -15820,6 +16448,17 @@ async function salvageVerdictFromPrComment(deps, rv, reviewsRunId, since, ctx) {
15820
16448
  }
15821
16449
  return true;
15822
16450
  }
16451
+ async function salvageVerifyVerdictFromPrComment(deps, vr, verifiesRunId, since, how) {
16452
+ if (!deps.verifyVerdictComment || !vr.pr_url) return false;
16453
+ const found = await deps.verifyVerdictComment(vr.pr_url, since).catch(() => null);
16454
+ if (!found) return false;
16455
+ const verdictText = verifyVerdictLabel(found.verdict);
16456
+ deps.db.updateRun(verifiesRunId, { verify_verdict: found.verdict });
16457
+ deps.db.addEvent(verifiesRunId, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u043F\u043E\u0434\u0445\u0432\u0430\u0447\u0435\u043D \u0438\u0437 PR-\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430 (${how}): ${verdictText}`);
16458
+ await closeReviewerRun(deps, vr, `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u044B\u043D\u0435\u0441\u0435\u043D \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C (${verdictText}), ${how} \u2014 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u0437\u0430\u043A\u0440\u044B\u0442\u0430 \u0431\u0435\u0437 \u0441\u043F\u0430\u0432\u043D\u0430 \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430`);
16459
+ narrate(deps, `${droneLabel(vr)} \u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 (${verdictText}) \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u0430\u043B, \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E.`);
16460
+ return true;
16461
+ }
15823
16462
  async function failReviewerRun(deps, reviewer, reviewsRunId, opts) {
15824
16463
  if (!opts.keepWorktree && reviewer.worktree_path) {
15825
16464
  await deps.removeWorktree(reviewer.repo, reviewer.worktree_path).catch(() => {
@@ -16022,6 +16661,7 @@ async function tickOnce(deps) {
16022
16661
  stormActive = await manageStorm(deps, now, worktreeExists, hasTranscript);
16023
16662
  }
16024
16663
  const droneCap = deps.maxDrones ? await deps.maxDrones() : Infinity;
16664
+ if (stormActive) noteStormStall(deps);
16025
16665
  if (deps.resourcesOk) {
16026
16666
  await manageRelief(deps, now, droneCap, stormActive);
16027
16667
  } else if (!stormActive) {
@@ -16065,6 +16705,26 @@ async function tickOnce(deps) {
16065
16705
  narrate(deps, `${droneLabel(run)} \u0432\u044B\u043D\u0435\u0441 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0440\u0435\u0432\u044C\u044E \u2014 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442.`);
16066
16706
  continue;
16067
16707
  }
16708
+ if (run.verifies_run_id != null) {
16709
+ const verified = deps.db.getRun(run.verifies_run_id);
16710
+ const sameCycle = verified != null && (verified.review_cycle ?? 0) === (run.review_cycle ?? 0);
16711
+ if (verified && sameCycle && verified.verify_verdict == null) {
16712
+ if (await salvageVerifyVerdictFromPrComment(deps, run, run.verifies_run_id, run.created_at, "\u043F\u043E\u0437\u0432\u0430\u043B done(), \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B")) continue;
16713
+ if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
16714
+ });
16715
+ deps.db.updateRun(run.id, { status: "failed" });
16716
+ deps.db.addEvent(run.id, "failed", "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0437\u0430\u043A\u0440\u044B\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u0430\u0441\u044C");
16717
+ deps.db.addEvent(run.verifies_run_id, "step", `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 ${run.name} (#${run.id}) \u0437\u0430\u043A\u0440\u044B\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434`);
16718
+ narrate(deps, `${droneLabel(run)} \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B, \u043D\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438, \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.`);
16719
+ continue;
16720
+ }
16721
+ if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
16722
+ });
16723
+ deps.db.updateRun(run.id, { status: "done" });
16724
+ deps.db.addEvent(run.id, "done", "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430, \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u044B\u043D\u0435\u0441\u0435\u043D");
16725
+ narrate(deps, `${droneLabel(run)} \u0432\u044B\u043D\u0435\u0441 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u2014 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442.`);
16726
+ continue;
16727
+ }
16068
16728
  if (!run.pr_url) continue;
16069
16729
  const prUrl = run.pr_url;
16070
16730
  const st = await deps.prStatus(prUrl);
@@ -16125,7 +16785,7 @@ async function tickOnce(deps) {
16125
16785
  if (run.review_verdict === "changes" && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id) && await wakeAuthorOnChanges(deps, run, now, droneCap)) {
16126
16786
  continue;
16127
16787
  }
16128
- if (!st.conflicting && !st.isDraft && deps.spawnReviewer && !stormActive && (!deps.reviewCutoffAt || run.updated_at >= deps.reviewCutoffAt) && deps.db.reviewerCountFor(run.id) === 0 && // ГЕЙТ ПЕРВЫМ: живой/успешный ревьюер уже есть — ни капа, ни спавна не трогаем
16788
+ const reviewerGatesOk = !st.conflicting && !st.isDraft && !!deps.spawnReviewer && !stormActive && (!deps.reviewCutoffAt || run.updated_at >= deps.reviewCutoffAt) && deps.db.reviewerCountFor(run.id) === 0 && // ГЕЙТ ПЕРВЫМ: живой/успешный ревьюер уже есть — ни капа, ни спавна не трогаем
16129
16789
  // …и НИ ОДНОГО живого/ждущего ревьюера ЛЮБОГО цикла (TASWM-299). reviewerCountFor
16130
16790
  // выше скоупится по review_cycle, поэтому сама по себе она не инвариант «один
16131
16791
  // ревьюер на PR», а лишь идемпотентность внутри круга: фикс-итерация бампит цикл, и
@@ -16133,7 +16793,8 @@ async function tickOnce(deps) {
16133
16793
  // такого уже закрыла (тот же тик, выше) — этот гейт держит инвариант СТРУКТУРНО, а
16134
16794
  // не порядком веток в 900-строчной функции: увидели живого — значит закрывать его
16135
16795
  // рано, спавнить второго нельзя.
16136
- liveOrWaitingReviewers(deps.db, run.id).length === 0) {
16796
+ liveOrWaitingReviewers(deps.db, run.id).length === 0;
16797
+ if (reviewerGatesOk && deps.spawnReviewer && await projectWantsReviewer(deps, run)) {
16137
16798
  if (run.review_cycle >= REVIEW_CYCLE_LIMIT) {
16138
16799
  if (!deps.db.events(run.id).some((e) => e.kind === "step" && (e.text ?? "").includes(REVIEW_CYCLE_ESCALATION_MARK))) {
16139
16800
  deps.db.addEvent(run.id, "step", `${REVIEW_CYCLE_ESCALATION_MARK}: ${run.review_cycle} \u0446\u0438\u043A\u043B(\u043E\u0432) \u0440\u0435\u0432\u044C\u044E-\u0444\u0438\u043A\u0441\u043E\u0432 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`);
@@ -16202,6 +16863,7 @@ async function tickOnce(deps) {
16202
16863
  }
16203
16864
  await closeMergedTaskRuns(deps);
16204
16865
  await closeOrphanedReviewers(deps);
16866
+ await spawnVerifiersForOpenPrs(deps, stormActive);
16205
16867
  refreshReviewPhases(deps);
16206
16868
  let liveChildPpids;
16207
16869
  try {
@@ -16213,7 +16875,7 @@ async function tickOnce(deps) {
16213
16875
  for (const run of [...deps.db.listRuns("running"), ...deps.db.listRuns("blocked")]) {
16214
16876
  const alive = run.pid != null && deps.isAlive(run.pid);
16215
16877
  if (!alive && deps.db.takeRun && !await deps.db.takeRun(run.id)) continue;
16216
- if (!alive && !await prodStillAllows(deps, run, ["running", "blocked"])) continue;
16878
+ if (!alive && await prodOverrideReason(deps, run, ["running", "blocked"])) continue;
16217
16879
  const outcome = alive ? "unknown" : deps.exitOutcome?.(run) ?? "unknown";
16218
16880
  if (!alive && deps.accountCost) {
16219
16881
  try {
@@ -16284,7 +16946,7 @@ async function tickOnce(deps) {
16284
16946
  continue;
16285
16947
  }
16286
16948
  }
16287
- if (!alive && outcome === "graceful" && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.kind !== "hero") {
16949
+ if (!alive && outcome === "graceful" && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && run.kind !== "hero") {
16288
16950
  const wt = run.worktree_path;
16289
16951
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
16290
16952
  if (deps.respawn && canContinue && run.respawns < RESPAWN_LIMIT) {
@@ -16309,7 +16971,7 @@ async function tickOnce(deps) {
16309
16971
  narrate(deps, `${droneLabel(run)} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B \u0441\u0435\u0441\u0441\u0438\u044E \u0441\u0430\u043C, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \xAB\u0437\u0430\u043C\u0435\u0440\xBB, \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0432 \u043B\u043E\u0433\u0435 (TASWM-229).`);
16310
16972
  continue;
16311
16973
  }
16312
- if (!alive && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && deps.respawn && run.respawns < RESPAWN_LIMIT) {
16974
+ if (!alive && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && deps.respawn && run.respawns < RESPAWN_LIMIT) {
16313
16975
  const wt = run.worktree_path;
16314
16976
  const wtAlive = !!wt && worktreeExists(wt);
16315
16977
  const canContinue = wtAlive && hasTranscript(wt);
@@ -16345,6 +17007,10 @@ async function tickOnce(deps) {
16345
17007
  });
16346
17008
  if (salvaged) continue;
16347
17009
  }
17010
+ if (!alive && run.status === "running" && run.verifies_run_id != null && run.pr_url && deps.verifyVerdictComment) {
17011
+ const salvaged = await salvageVerifyVerdictFromPrComment(deps, run, run.verifies_run_id, run.created_at, "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0432\u044B\u0448\u0435\u043B, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done()");
17012
+ if (salvaged) continue;
17013
+ }
16348
17014
  if (!alive && run.status === "running" && run.reviews_run_id != null && run.pr_url && deps.ci) {
16349
17015
  const parent = deps.db.getRun(run.reviews_run_id);
16350
17016
  const ciWatchOff = parent != null && ciWatchClosed(deps.db, parent);
@@ -16435,9 +17101,17 @@ async function tickOnce(deps) {
16435
17101
  const az = deps.anomalies;
16436
17102
  let ready = [];
16437
17103
  try {
17104
+ const autoReviewSnapshot = /* @__PURE__ */ new Map();
17105
+ if (deps.autoReviewForRepo && az.autoReviewEnabled) {
17106
+ for (const r of deps.db.listRuns("review")) {
17107
+ if (!r.pr_url || autoReviewSnapshot.has(r.repo)) continue;
17108
+ autoReviewSnapshot.set(r.repo, await deps.autoReviewForRepo(effectiveRepo(deps, r)).catch(() => true));
17109
+ }
17110
+ }
16438
17111
  const candidates = [
16439
17112
  ...buildAnomalyCandidates(deps.db, now, az.thresholds, {
16440
- autoReviewEnabled: az.autoReviewEnabled,
17113
+ // Машинный ответ, уточнённый проектом (TASWM-396): нет снимка — как раньше.
17114
+ autoReviewEnabled: (repo) => az.autoReviewEnabled && (autoReviewSnapshot.get(repo) ?? true),
16441
17115
  ...az.outboxPendingCount ? { outboxPendingCount: az.outboxPendingCount() } : {},
16442
17116
  // resource-gate-stall (TASWM-212): та же причина, что видит лог manageRelief — один
16443
17117
  // deps.gateReason() на тик (кэш hostReading() внутри resources.ts, лишнего спавна нет).
@@ -16633,6 +17307,76 @@ async function spawnReviewer(deps, reviewRun, files) {
16633
17307
  throw e;
16634
17308
  }
16635
17309
  }
17310
+ var VERIFY_SCOPE_BLOCK = [
17311
+ "\u{1F4B8} \u041A\u041E\u041D\u0422\u0423\u0420 \u0412\u0415\u0420\u0418\u0424\u0418\u041A\u0410\u0426\u0418\u0418 (\u0436\u0451\u0441\u0442\u043A\u043E). \u0421\u0435\u0441\u0441\u0438\u044F \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430 \u0431\u044E\u0434\u0436\u0435\u0442\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0438 \u0441\u0440\u0435\u0436\u0435\u0442 \u0442\u0435\u0431\u044F \u0431\u0435\u0437 \u043F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u044F \u2014 \u0443\u043F\u0440\u0451\u0448\u044C\u0441\u044F, \u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u043E\u0432\u0441\u0435. \u041F\u043E\u044D\u0442\u043E\u043C\u0443:",
17312
+ "\u2022 \u0422\u044B \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0448\u044C \u0417\u0410\u042F\u0412\u041B\u0415\u041D\u041D\u041E\u0415, \u0430 \u043D\u0435 \u0432\u0435\u0441\u044C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439. \u0421\u043F\u0438\u0441\u043E\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u2014 \u0438\u0437 \u043E\u0442\u0447\u0451\u0442\u0430 \u0434\u0440\u043E\u043D\u0430 \u0438 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438; \u0432\u0441\u0451 \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u043E\u0435 \u0442\u0440\u043E\u0433\u0430\u0439, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u043D\u043E \u043B\u043E\u043C\u0430\u0435\u0442\u0441\u044F \u0442\u0432\u043E\u0435\u0439 \u0436\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u043E\u0439.",
17313
+ "\u2022 \u041F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u2014 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u0434\u0432\u0443\u0445 \u0437\u0430\u0445\u043E\u0434\u043E\u0432. \u041D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u043E\u0441\u044C \u2014 \u044D\u0442\u043E \u0438 \u0435\u0441\u0442\u044C \u0447\u0430\u0441\u0442\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 (\xAB\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u2026\xBB), \u0430 \u043D\u0435 \u043F\u043E\u0432\u043E\u0434 \u0447\u0438\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442.",
17314
+ "\u2022 \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u044B\u0432\u0430\u0439, \u043E\u0434\u0438\u043D \u0432\u043E\u043F\u0440\u043E\u0441 \u2014 \u043E\u0434\u0438\u043D \u0437\u0430\u0445\u043E\u0434. \u041D\u0435 \u0441\u043E\u0448\u043B\u043E\u0441\u044C \u0441\u043E \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u0440\u0430\u0437\u0430 \u2014 \u0437\u0430\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u043A\u0430\u043A \u043D\u0430\u0445\u043E\u0434\u043A\u0443, \u0430 \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0439 \u0442\u0440\u0435\u0442\u0438\u0439 \u043A\u0440\u0443\u0433.",
17315
+ "\u2022 \u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0441\u043F\u0435\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0412\u0421\u0415\u0413\u0414\u0410 \u043B\u0443\u0447\u0448\u0435 \u0438\u0434\u0435\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435 \u0431\u0443\u0434\u0435\u0442. \u0427\u0435\u0441\u0442\u043D\u043E \u043D\u0430\u0437\u043E\u0432\u0438, \u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u043C\u043E\u0442\u0440\u0435\u043B."
17316
+ ].join("\n");
17317
+ function buildVerifierPrompt(verifiedRun, verifier, branch, files, attachmentsBlock, verifyMode = SWARM_VERIFY) {
17318
+ const name = verifier.name || `#${verifier.id}`;
17319
+ const prUrl = verifiedRun.pr_url ?? "(\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR \u2014 \u0441\u043C. \u043B\u043E\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430)";
17320
+ const taskUrl = verifiedRun.task_url ? ` \u0417\u0430\u0434\u0430\u0447\u0430: ${verifiedRun.task_url}.` : "";
17321
+ const filesBlock = files.length ? `\u0424\u0430\u0439\u043B\u044B \u0434\u0438\u0444\u0444\u0430 (${files.length}): ${files.slice(0, 40).join(", ")}${files.length > 40 ? " \u2026" : ""}.` : "\u0421\u043F\u0438\u0441\u043E\u043A \u0444\u0430\u0439\u043B\u043E\u0432 \u0434\u0438\u0444\u0444\u0430 gh \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u2014 \u0432\u043E\u0437\u044C\u043C\u0438 \u0435\u0433\u043E \u0441\u0430\u043C: `gh pr diff --name-only`.";
17322
+ return [
17323
+ `\u0422\u044B \u2014 \u0434\u0440\u043E\u043D-\u0412\u0415\u0420\u0418\u0424\u0418\u041A\u0410\u0422\u041E\u0420 ${name} \u0440\u043E\u044F, headless-\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C (\u041D\u0415 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u0447\u0430\u0442). \u0420\u043E\u0439 \u043F\u043E\u0434\u043D\u044F\u043B \u0442\u0435\u0431\u044F \u0441\u0430\u043C \u043D\u0430 PR \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F: \u0442\u044B \u043E\u0442 \u043D\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0448\u044C \u0438 \u0435\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0435\u0448\u044C.`,
17324
+ "",
17325
+ HEADLESS_BLOCK,
17326
+ "",
17327
+ `\u0417\u0410\u0414\u0410\u0427\u0410: \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u041F\u0420\u0410\u0412\u0414\u0410 \u041B\u0418 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u0442\u043E, \u0447\u0442\u043E \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043E \u0432 PR ${prUrl} \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${verifiedRun.task_slug} (\u0434\u0440\u043E\u043D ${verifiedRun.name}).${taskUrl} \u0422\u044B \u0432 git-worktree \u0441 \u0447\u0435\u043A\u0430\u0443\u0442\u043E\u043C \u0432\u0435\u0442\u043A\u0438 \`${branch}\` (detached, \u043A\u043E\u043D\u0447\u0438\u043A origin) \u2014 \u043A\u043E\u0434 \u0443\u0436\u0435 \u043D\u0430 \u0434\u0438\u0441\u043A\u0435.`,
17328
+ ...verifiedRun.base_branch ? [`\u26A0\uFE0F PR \u043E\u0442\u043A\u0440\u044B\u0442 \u0432 \u0432\u0435\u0442\u043A\u0443 \`${verifiedRun.base_branch}\`, \u0430 \u043D\u0435 \u0432 \u0432\u0435\u0442\u043A\u0443 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: \u0441\u0440\u0430\u0432\u043D\u0438\u0432\u0430\u0439 \u0441 \u043D\u0435\u0439 (\`git diff origin/${verifiedRun.base_branch}...HEAD\`), \u0438\u043D\u0430\u0447\u0435 \u043F\u0440\u0438\u043C\u0435\u0448\u044C \u0447\u0443\u0436\u0438\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043F\u0430\u0447\u043A\u0438 \u0437\u0430 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u044D\u0442\u043E\u0433\u043E \u0434\u0440\u043E\u043D\u0430.`] : [],
17329
+ filesBlock,
17330
+ "",
17331
+ "\u26A0\uFE0F \u0422\u0412\u041E\u042F \u0417\u0410\u0414\u0410\u0427\u0410 \u2014 \u041E\u041F\u0420\u041E\u0412\u0415\u0420\u0413\u041D\u0423\u0422\u042C, \u0410 \u041D\u0415 \u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042C. \u0410\u0432\u0442\u043E\u0440 \u0440\u0430\u0431\u043E\u0442\u044B \u2014 \u043F\u043B\u043E\u0445\u043E\u0439 \u0435\u0451 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0438\u0439: \u043E\u043D \u0438\u0449\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043B. \u0422\u044B \u043D\u0430\u043D\u044F\u0442 \u043D\u0430 \u043E\u0431\u0440\u0430\u0442\u043D\u043E\u0435. \u0418\u0441\u0445\u043E\u0434\u0438 \u0438\u0437 \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435 \u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442, \u0438 \u0434\u043E\u0431\u0440\u043E\u0441\u043E\u0432\u0435\u0441\u0442\u043D\u043E \u043F\u043E\u043F\u044B\u0442\u0430\u0439\u0441\u044F \u044D\u0442\u043E \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C. \u041D\u0435 \u043D\u0430\u0448\u0451\u043B \u2014 \u0442\u043E\u0433\u0434\u0430 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E\u0433\u0434\u0430 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0439.",
17332
+ "\u26A0\uFE0F \u0424\u0410\u0419\u041B\u042B \u041D\u0415 \u041F\u0420\u0410\u0412\u0418\u0428\u042C, \u041A\u041E\u041C\u041C\u0418\u0422\u042B \u041D\u0415 \u041F\u0423\u0428\u0418\u0428\u042C. \u0427\u0438\u043D\u0438\u0442\u044C \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u0435 \u0431\u0443\u0434\u0435\u0442 \u0430\u0432\u0442\u043E\u0440. \u0422\u0432\u043E\u0439 \u043F\u0440\u043E\u0434\u0443\u043A\u0442 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442.",
17333
+ "\u26A0\uFE0F \u041F\u0415\u0420\u0415\u0421\u041A\u0410\u0417 \u0414\u0418\u0424\u0424\u0410 \u2014 \u041D\u0415 \u0412\u0415\u0420\u0414\u0418\u041A\u0422. \xAB\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0433\u043B\u044F\u0434\u044F\u0442 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\xBB, \xAB\u043A\u043E\u0434 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0435\xBB \u2014 \u044D\u0442\u043E \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430, \u0430 \u043C\u043D\u0435\u043D\u0438\u0435 \u043E \u0442\u0435\u043A\u0441\u0442\u0435. \u0414\u0438\u0444\u0444 \u0443\u0436\u0435 \u0447\u0438\u0442\u0430\u0435\u0442 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0439 \u0434\u0440\u043E\u043D-\u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u0442\u044B \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0448\u044C \u0437\u0430 \u041F\u041E\u0412\u0415\u0414\u0415\u041D\u0418\u0415. \u041A\u0430\u0436\u0434\u044B\u0439 \u043F\u0443\u043D\u043A\u0442 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043E\u0431\u044F\u0437\u0430\u043D \u043E\u043F\u0438\u0440\u0430\u0442\u044C\u0441\u044F \u043D\u0430 \u0442\u043E, \u0447\u0442\u043E \u0442\u044B \u0417\u0410\u041F\u0423\u0421\u0422\u0418\u041B \u0438 \u0423\u0412\u0418\u0414\u0415\u041B (\u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u043B\u043E\u0433, \u043E\u0442\u0432\u0435\u0442 \u0440\u0443\u0447\u043A\u0438, \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435 \u044D\u043A\u0440\u0430\u043D\u0430).",
17334
+ "",
17335
+ VERIFY_SCOPE_BLOCK,
17336
+ ...attachmentsBlock ? ["", attachmentsBlock] : [],
17337
+ "",
17338
+ "\u041F\u043E\u0440\u044F\u0434\u043E\u043A:",
17339
+ "1. \u0412\u044B\u044F\u0441\u043D\u0438, \u0427\u0422\u041E \u0418\u041C\u0415\u041D\u041D\u041E \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043E: `gh pr view " + prUrl + " --body` (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 PR) + read_task() (\u0442\u0430\u043C \u0436\u0435 \u043E\u0442\u0447\u0451\u0442 \u0434\u0440\u043E\u043D\u0430 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0438 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438). \u0412\u044B\u043F\u0438\u0448\u0438 \u0441\u0435\u0431\u0435 \u0441\u043F\u0438\u0441\u043E\u043A \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0445 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0445 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439 \u2014 \u043F\u043E \u043D\u0438\u043C \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C.",
17340
+ "2. \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u0415\u0414\u0418 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435. \u0422\u0435\u0441\u0442\u044B \u2014 \u043D\u0435 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u0441\u0430\u043C\u0438 \u043F\u043E \u0441\u0435\u0431\u0435: \u0438\u0445 \u043F\u0438\u0441\u0430\u043B \u0442\u043E\u0442 \u0436\u0435 \u0430\u0432\u0442\u043E\u0440. \u0413\u0434\u0435 \u043C\u043E\u0436\u043D\u043E \u2014 \u0433\u043E\u043D\u044F\u0439 \u0444\u0438\u0447\u0443 \u0442\u0430\u043A, \u043A\u0430\u043A \u0435\u0451 \u0443\u0432\u0438\u0434\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A (\u043F\u043E\u0434\u043D\u0438\u043C\u0438 \u0441\u0442\u0435\u043A/\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u0443/\u0434\u0451\u0440\u043D\u0438 \u0440\u0443\u0447\u043A\u0443; \u0440\u0435\u0446\u0435\u043F\u0442 \u2014 README \u0438 \u0441\u043A\u0438\u043B\u043B\u044B \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F). \u041D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u2014 \u044D\u0442\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \xAB\u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0441\u044F\xBB, \u0438 \u043E\u043D \u0446\u0435\u043D\u043D\u0435\u0435 \u0432\u0435\u0436\u043B\u0438\u0432\u043E\u0433\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.",
17341
+ "3. \u041F\u041E\u0418\u0429\u0418, \u0427\u0422\u041E \u0421\u041B\u041E\u041C\u0410\u041B\u041E\u0421\u042C \u0420\u042F\u0414\u041E\u041C. \u0421\u043E\u0441\u0435\u0434\u043D\u0438\u0435 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0438 \u0442\u043E\u0439 \u0436\u0435 \u0437\u043E\u043D\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0434\u0438\u0444\u0444 \u0437\u0430\u0434\u0435\u043B \u043A\u043E\u0441\u0432\u0435\u043D\u043D\u043E; \u0441\u0442\u0430\u0440\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 (\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u2014 \u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u043E\u0441\u0442\u044C \u0438 \u043E\u0442\u043A\u0430\u0442); \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u0432\u044B\u0437\u043E\u0432 (\u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C); \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u044B\u0439 \u0432\u044B\u0437\u043E\u0432. " + (verifyMode === "local" ? "\u0413\u0435\u0439\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u0441\u0430\u043C (\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 verifyMode=local, CI \u043C\u043E\u0436\u0435\u0442 \u043D\u0435 \u0431\u044B\u0442\u044C \u0432\u043E\u0432\u0441\u0435 \u2014 \u0435\u0433\u043E \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u041D\u0415 \u0434\u0435\u0444\u0435\u043A\u0442 PR \u0438 \u0447\u0438\u043D\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0434\u043E)." : "\u0422\u0435\u0441\u0442-\u0441\u044C\u044E\u0442 \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u043D\u0435 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0439 \u2014 \u0435\u0433\u043E \u0443\u0436\u0435 \u043F\u0440\u043E\u0433\u043D\u0430\u043B CI \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 (`gh pr checks " + prUrl + "`); \u0442\u0432\u043E\u0451 \u0434\u0435\u043B\u043E \u0442\u043E, \u0447\u0435\u0433\u043E \u0432 CI \u043D\u0435\u0442."),
17342
+ "4. \u041F\u0420\u041E\u0414\u0410\u0412\u0418 \u041A\u0420\u0410\u0419\u041D\u0418\u0415 \u0421\u041B\u0423\u0427\u0410\u0418: \u043F\u0443\u0441\u0442\u043E/\u043D\u043E\u043B\u044C/\u043E\u0447\u0435\u043D\u044C \u0434\u043B\u0438\u043D\u043D\u043E\u0435, \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u043F\u0440\u0430\u0432\u0430, \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430\u044F \u0441\u0435\u0442\u044C, \u0433\u043E\u043D\u043A\u0430, \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0430\u044F \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0430. \u0425\u043E\u0442\u044F \u0431\u044B \u0434\u0432\u0430 \u0442\u0430\u043A\u0438\u0445 \u0437\u0430\u0445\u043E\u0434\u0430 \u2014 \u044D\u0442\u043E \u043C\u0438\u043D\u0438\u043C\u0443\u043C \u0434\u043E\u0431\u0440\u043E\u0441\u043E\u0432\u0435\u0441\u0442\u043D\u043E\u0439 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u043E\u043F\u0440\u043E\u0432\u0435\u0440\u0433\u043D\u0443\u0442\u044C.",
17343
+ `5. \u041E\u0441\u0442\u0430\u0432\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u043D\u0430 PR: \`gh pr comment ${prUrl} --body "..."\` (\u041D\u0415 \`gh pr review\` \u2014 GitHub \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442 \u0435\u0433\u043E \u043D\u0430 \u0441\u0432\u043E\u0451\u043C PR). \u041F\u0415\u0420\u0412\u041E\u0419 \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \u2014 \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u043D\u043E \u0438\u0437 \u0417\u0410\u0413\u041B\u0410\u0412\u041D\u042B\u041C\u0418: **\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E** / **\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F** / **\u041D\u0410\u0428\u0401\u041B**. \u0414\u0430\u043B\u044C\u0448\u0435: \u0447\u0442\u043E \u0438\u043C\u0435\u043D\u043D\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B (\u043A\u043E\u043C\u0430\u043D\u0434\u0430\u043C\u0438), \u0447\u0442\u043E \u0443\u0432\u0438\u0434\u0435\u043B, \u0438 \u2014 \u0434\u043B\u044F \u0434\u0432\u0443\u0445 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0445 \u2014 \u043A\u0430\u043A \u044D\u0442\u043E \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C.`,
17344
+ `6. \u0412\u044B\u0437\u043E\u0432\u0438 done(${prUrl}, verdict: "confirmed" | "not_reproduced" | "found_issues", summary: "\u0447\u0442\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B, \u0447\u0442\u043E \u0443\u0432\u0438\u0434\u0435\u043B, \u0447\u0442\u043E \u0440\u0435\u0448\u0438\u043B"). \u0420\u043E\u0439 \u0437\u0430\u043F\u0438\u0448\u0435\u0442 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0438 \u0440\u0430\u0437\u043D\u0435\u0441\u0451\u0442 summary \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u043A\u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0435\u0439 \u043F\u0440\u0438\u0451\u043C\u043A\u0443 \u2014 \u0442\u0440\u0435\u0442\u044C\u0435\u0433\u043E \u0438\u0441\u0445\u043E\u0434\u0430 \u043D\u0435\u0442: \u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0432\u043E\u0432\u0441\u0435 \u2014 \u044D\u0442\u043E "not_reproduced" \u0441 \u0447\u0435\u0441\u0442\u043D\u043E\u0439 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \u0432 summary.`,
17345
+ "",
17346
+ "\u0427\u0442\u043E \u041D\u0415 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u043E\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u043C: \u0432\u043A\u0443\u0441\u043E\u0432\u044B\u0435 \u043F\u0440\u0430\u0432\u043A\u0438 \u043A\u043E\u0434\u0430, \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 CI \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, \u0447\u0443\u0436\u0438\u0435 \u0441\u043B\u043E\u043C\u0430\u043D\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B, \u043D\u0435 \u043E\u0442\u043D\u043E\u0441\u044F\u0449\u0438\u0435\u0441\u044F \u043A \u044D\u0442\u043E\u043C\u0443 \u0434\u0438\u0444\u0444\u0443. \u0417\u0430\u043C\u0435\u0442\u0438\u043B \u0442\u0430\u043A\u043E\u0435 \u2014 \u0441\u043A\u0430\u0436\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439 (\u0438\u043B\u0438 \u0447\u0435\u0440\u0435\u0437 note), \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043E\u0442 \u044D\u0442\u043E\u0433\u043E \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442."
17347
+ ].join("\n");
17348
+ }
17349
+ async function spawnVerifier(deps, verifiedRun, files) {
17350
+ const repo = normalizeRepo(verifiedRun.repo || deps.repoFallback);
17351
+ const branch = await worktreeBranchFor(verifiedRun, deps.prHeadBranch);
17352
+ const verifyMode = deps.verifyMode ? await deps.verifyMode(repo).catch(() => void 0) : void 0;
17353
+ const verifier = deps.db.createRun({
17354
+ task_slug: verifiedRun.task_slug,
17355
+ repo,
17356
+ kind: "verifier",
17357
+ verifies_run_id: verifiedRun.id,
17358
+ review_cycle: verifiedRun.review_cycle,
17359
+ // штамп цикла: фикс-итерацию проверяет свежий верификатор
17360
+ workspace_id: verifiedRun.workspace_id
17361
+ });
17362
+ const wt = join29(deps.worktreesDir, basename4(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
17363
+ try {
17364
+ await deps.git.prepareDetachedWorktree(repo, branch, wt);
17365
+ await deps.git.setWorktreeIdentity(wt, verifier.name);
17366
+ const attachments = await deps.taskAttachments?.(verifiedRun.task_slug, wt, verifiedRun.workspace_id);
17367
+ const prompt = buildVerifierPrompt(verifiedRun, verifier, branch, files, attachments, verifyMode);
17368
+ const pid = deps.launch({ worktree: wt, run: verifier, prompt });
17369
+ deps.db.updateRun(verifier.id, { status: "running", worktree_path: wt, branch, pid, pr_url: verifiedRun.pr_url });
17370
+ deps.db.addEvent(verifiedRun.id, "step", `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u0434\u043D\u044F\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 ${verifier.name} (#${verifier.id}) \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435 \u043D\u0430 \u043E\u043F\u0440\u043E\u0432\u0435\u0440\u0436\u0435\u043D\u0438\u0435`);
17371
+ return deps.db.getRun(verifier.id);
17372
+ } catch (e) {
17373
+ await deps.git.removeWorktree(repo, wt).catch(() => {
17374
+ });
17375
+ deps.db.updateRun(verifier.id, { status: "failed" });
17376
+ deps.db.addEvent(verifier.id, "failed", `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C: ${String(e)}`);
17377
+ throw e;
17378
+ }
17379
+ }
16636
17380
  async function respawnRun(deps, run, mode, nudge) {
16637
17381
  const repo = normalizeRepo(run.repo || deps.repoFallback);
16638
17382
  const wt = run.worktree_path || (mode === "fresh" ? droneWorktreePath(deps.worktreesDir, repo, run.task_slug) : null);
@@ -16751,7 +17495,11 @@ async function makeStore(repo, worktreesDir, repoFlag) {
16751
17495
  }
16752
17496
  const call = resilientSwarmMcp({
16753
17497
  onDown: (ms) => console.warn(`[overmind] \u043F\u0440\u043E\u0434-MCP \u043C\u043E\u043B\u0447\u0438\u0442 ${Math.round(ms / 1e3)}\u0441 \u2014 \u0440\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E, \u0441\u0434\u0430\u0447\u0438 \u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u044B \u043D\u0435 \u0434\u043E\u0435\u0437\u0436\u0430\u044E\u0442.`),
16754
- onUp: () => console.warn("[overmind] \u043F\u0440\u043E\u0434-MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
17498
+ onUp: () => console.warn("[overmind] \u043F\u0440\u043E\u0434-MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438."),
17499
+ // Ретрай не молчит: обрыв, переигранный в том же вызове, всё равно виден.
17500
+ // Без этой строки самолечение связи — единственный путь, о котором в логе
17501
+ // ни слова, и разбор «почему тик подтормозил» упирается в тишину.
17502
+ onRetry: ({ tool, attempt, reason }) => console.warn(`[overmind] \u043F\u0440\u043E\u0434-MCP: ${tool} \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F (${reason}) \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 ${attempt}.`)
16755
17503
  });
16756
17504
  const explicitRegistry = await buildRepoRegistry(repoPaths);
16757
17505
  if (repoPaths.length > 0 && explicitRegistry.length < repoPaths.length) {
@@ -16793,6 +17541,8 @@ async function makeStore(repo, worktreesDir, repoFlag) {
16793
17541
  reconciled: rec.reconciled,
16794
17542
  host,
16795
17543
  repoMap,
17544
+ prodMcp: call,
17545
+ // TASWM-325: состояние связи для `swarm status`
16796
17546
  // Периодическая пересборка (шаг 0a4 тика): применяем новый реестр к ЖИВОМУ клиенту —
16797
17547
  // и список для claim/heartbeat, и обратную карту owner/repo → путь.
16798
17548
  refreshRepoMap: async () => {
@@ -16861,7 +17611,8 @@ async function runDaemon(opts) {
16861
17611
  reconciled,
16862
17612
  host,
16863
17613
  repoMap: repoMapState,
16864
- refreshRepoMap: rebuildRepoMap
17614
+ refreshRepoMap: rebuildRepoMap,
17615
+ prodMcp
16865
17616
  } = await makeStore(opts.repo, worktreesDir, opts.repoFlag);
16866
17617
  await preflightAuth();
16867
17618
  const outdated = outdatedLine(PACKAGE_VERSION, await latestRelease);
@@ -16966,7 +17717,15 @@ async function runDaemon(opts) {
16966
17717
  const client = db;
16967
17718
  heartbeat = {
16968
17719
  push: async () => {
16969
- const reply = await client.heartbeat(daemonVersion?.get() ?? null, control.state ?? null, lastOwnCapacity, hostResourcesForProd());
17720
+ const stormActive = client.stormActive();
17721
+ const stormStartedAt = stormActive ? client.currentStormStartedAt() : null;
17722
+ const reply = await client.heartbeat(
17723
+ daemonVersion?.get() ?? null,
17724
+ control.state ?? null,
17725
+ lastOwnCapacity,
17726
+ hostResourcesForProd(),
17727
+ { active: stormActive, reason: stormStartedAt ? `\u043D\u043E\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441 ${stormStartedAt} UTC` : null }
17728
+ );
16970
17729
  if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
16971
17730
  if (reply.commands.length) onHostCommands(reply.commands);
16972
17731
  if (reply.logRequests.length) {
@@ -17180,6 +17939,36 @@ async function runDaemon(opts) {
17180
17939
  verdictCommentFn = (prUrl, sinceIso) => latestVerdictComment(prUrl, sinceIso);
17181
17940
  reviewCutoffAtValue = reviewCutoffAt();
17182
17941
  }
17942
+ let spawnVerifierFn;
17943
+ let verifyVerdictCommentFn;
17944
+ if (SWARM_AUTO_VERIFY) {
17945
+ verifyVerdictCommentFn = (prUrl, sinceIso) => latestVerifyVerdictComment(prUrl, sinceIso);
17946
+ spawnVerifierFn = (verifiedRun, files) => spawnVerifier(
17947
+ {
17948
+ db,
17949
+ repoFallback: opts.repo,
17950
+ worktreesDir,
17951
+ launch,
17952
+ git: { prepareDetachedWorktree, setWorktreeIdentity, removeWorktree },
17953
+ prHeadBranch,
17954
+ ...verifyModeReader ? { verifyMode: verifyModeReader } : {},
17955
+ ...tl?.fetchFile ? {
17956
+ taskAttachments: async (slug, worktree, workspaceId) => {
17957
+ const client = tl.forWorkspace(workspaceId);
17958
+ const task = await client.getTask(slug);
17959
+ return prepareTaskAttachments({
17960
+ worktree,
17961
+ refs: task?.attachments,
17962
+ fetchFile: client.fetchFile.bind(client)
17963
+ });
17964
+ }
17965
+ } : {}
17966
+ },
17967
+ verifiedRun,
17968
+ files
17969
+ ).then(() => {
17970
+ });
17971
+ }
17183
17972
  const narrator = makeOvermindNarrator();
17184
17973
  const bootPause = loadPauseState(SWARM_HOME);
17185
17974
  const control = { paused: bootPause.paused, state: bootPause.paused ? bootPause : null };
@@ -17474,7 +18263,10 @@ async function runDaemon(opts) {
17474
18263
  // Авто-ревью (TASWM-148): ключи есть только когда SWARM_AUTO_REVIEW включён (дефолт да) —
17475
18264
  // spawnReviewerFn/verdictCommentFn/reviewCutoffAtValue выставляются В ОДНОМ if(SWARM_AUTO_REVIEW)
17476
18265
  // блоке выше, поэтому один и тот же гейт на все три (без отдельных тернарных условий).
17477
- ...spawnReviewerFn ? { spawnReviewer: spawnReviewerFn, verdictComment: verdictCommentFn, reviewCap: SWARM_REVIEW_CAP, reviewCutoffAt: reviewCutoffAtValue } : {}
18266
+ ...spawnReviewerFn ? { spawnReviewer: spawnReviewerFn, verdictComment: verdictCommentFn, reviewCap: SWARM_REVIEW_CAP, reviewCutoffAt: reviewCutoffAtValue } : {},
18267
+ // Верификация (TASWM-344) — свой тумблер, независимый от авто-ревью: проверить, правда
18268
+ // ли работает заявленное, осмысленно и там, где ревью диффа выключено.
18269
+ ...spawnVerifierFn ? { spawnVerifier: spawnVerifierFn, verifyVerdictComment: verifyVerdictCommentFn } : {}
17478
18270
  };
17479
18271
  let web;
17480
18272
  let droneChannel = null;
@@ -17705,14 +18497,41 @@ async function runDaemon(opts) {
17705
18497
  // печатает `swarm status`. Без него человек снова гадал бы, какие репозитории машина
17706
18498
  // считает своими и в каком дереве работает при двух копиях одного клона.
17707
18499
  repoMap: () => repoMapRows(repoMapState),
18500
+ // TASWM-325: СВЯЗЬ С ПРОДОМ и ёмкость — то, чего `swarm status` не мог узнать ниоткуда.
18501
+ // Собираем из трёх источников, каждый из которых знает свою часть: отказ по токену —
18502
+ // authState (TASWM-324, он ловит 401 на подключении, где его только и видно), живо ли
18503
+ // соединение — транспорт (mcpResilient.link), буфер и момент удачного пульса — клиент.
18504
+ // Мерить связь по возврату прод-вызова НЕЛЬЗЯ: боевой каллер не бросает, а отдаёт
18505
+ // {isError:true}, и такой замер показывал бы «связь есть» на мёртвом проде.
18506
+ // В sqlite-режиме клиента нет вовсе — раздел честно молчит, а не рисует зелёное.
18507
+ ...db instanceof SwarmClient ? {
18508
+ link: () => ({
18509
+ ...composeLink({
18510
+ api: TASKLESS_API,
18511
+ authDenied: authStateLine(),
18512
+ transport: prodMcp?.link() ?? null,
18513
+ pending: db.pendingCount(),
18514
+ now: Date.now()
18515
+ }),
18516
+ heardAt: db.lastHeardAt()
18517
+ })
18518
+ } : {},
18519
+ // Потолок дронов — УЖЕ посчитанный тиком (lastDroneCap), без похода в прод из ручки
18520
+ // статуса: /status обязан отвечать мгновенно и при лежащем проде.
18521
+ maxDrones: () => lastDroneCap,
17708
18522
  // Гейт мержа (TASWM-355): ручка /merge-gate отвечает по ЖИВОЙ настройке autoReview того
17709
18523
  // проекта, которому принадлежит PR (ридер выше), а не по глобальному env машины. Именно
17710
18524
  // этот ответ и превращается в запрет: PreToolUse-хук на `gh pr merge` спрашивает его
17711
18525
  // через `swarm merge-gate` и отказывает в команде, пока вердикта ревьюера нет.
18526
+ // TASWM-396: тот же ридер решает и подъём ревьюера, не только мерж — иначе проектная
18527
+ // настройка «ревью не нужно» ничего не выключала, а машина продолжала платить.
18528
+ ...autoReviewProject ? { autoReviewForRepo: autoReviewProject } : {},
17712
18529
  mergeGate: makeMergeGateResolver({
17713
18530
  listRuns: () => db.listRuns(),
17714
18531
  ...autoReviewProject ? { autoReview: autoReviewProject } : {},
17715
- fallbackAutoReview: SWARM_AUTO_REVIEW
18532
+ fallbackAutoReview: SWARM_AUTO_REVIEW,
18533
+ autoVerify: SWARM_AUTO_VERIFY
18534
+ // TASWM-344: вторая ось приёмки — вердикт верификатора
17716
18535
  }),
17717
18536
  // Оператор-пауза + «Оживить» (TASWM-214 Фаза 2): тот же shared-флаг, что читает тик;
17718
18537
  // narrate — операторские действия видны в ленте роя; revive — сброс залипшего шторма.
@@ -17760,6 +18579,11 @@ async function runDaemon(opts) {
17760
18579
  console.log(
17761
18580
  `overmind \u043F\u043E\u0434\u043D\u044F\u0442. default-repo=${opts.repo || "(\u043D\u0435\u0442)"} backend=${SWARM_BACKEND} taskless=${tl ? "on" : "off (slug-only)"}`
17762
18581
  );
18582
+ const denied = authStateLine();
18583
+ if (denied) {
18584
+ console.error(`[overmind] \u26D4 ${denied}`);
18585
+ console.error("[overmind] \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u041D\u0415 \u0432\u044B\u0431\u0440\u0430\u043D \u2014 \u043E\u043D \u043D\u0430\u0432\u044F\u0437\u0430\u043D \u043E\u0442\u043A\u0430\u0437\u043E\u043C: \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443, \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox.");
18586
+ }
17763
18587
  console.log(`\u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430: http://127.0.0.1:${web.port} \u2190 \u043E\u0442\u043A\u0440\u043E\u0439 \u0438 \u0432\u043E\u0439\u0434\u0438 \u0447\u0435\u0440\u0435\u0437 Taskless`);
17764
18588
  const interval = opts.intervalMs ?? 4e3;
17765
18589
  writeHeartbeat(HEARTBEAT_PATH, Date.now());
@@ -17847,6 +18671,7 @@ export {
17847
18671
  STALL_FLOOR_MS,
17848
18672
  STALL_MS,
17849
18673
  STORM_DEAD_LIMIT,
18674
+ VERIFY_SPAWN_LIMIT,
17850
18675
  WATCHDOG_PARK_REASON,
17851
18676
  buildChangesWakeNudge,
17852
18677
  buildCiReadyNudge,
@@ -17856,6 +18681,7 @@ export {
17856
18681
  buildResolverPrompt,
17857
18682
  buildReviewChecklist,
17858
18683
  buildReviewerPrompt,
18684
+ buildVerifierPrompt,
17859
18685
  cancelRun,
17860
18686
  hasLiveDescendant,
17861
18687
  lastChangesSummary,
@@ -17870,6 +18696,7 @@ export {
17870
18696
  reviveStuck,
17871
18697
  runDaemon,
17872
18698
  spawnReviewer,
18699
+ spawnVerifier,
17873
18700
  syncCrashedOnStartup,
17874
18701
  tickOnce,
17875
18702
  verdictCommentMark,