@taskless-app/swarm 0.6.25 → 0.6.27

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 +84 -25
  2. package/dist/overmind.js +776 -419
  3. package/package.json +2 -2
package/dist/overmind.js CHANGED
@@ -9,17 +9,23 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // ../swarm-core/src/types.ts
12
+ function holdsFiles(run) {
13
+ if ((run.kind ?? "drone") !== "drone") return false;
14
+ if (run.status === "review") return !!run.pr_url;
15
+ return ACTIVE_DRONE_STATUSES.has(run.status);
16
+ }
12
17
  function isMilestoneKind(kind) {
13
18
  return MILESTONE_KIND_SET.has(kind);
14
19
  }
15
20
  function toRunKind(role) {
16
21
  return role === "hero" || role === "reviewer" || role === "verifier" ? role : "drone";
17
22
  }
18
- var ACTIVE_DRONE_STATUSES, EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
23
+ var ACTIVE_DRONE_STATUSES, FILE_HOLDING_STATUSES, EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
19
24
  var init_types = __esm({
20
25
  "../swarm-core/src/types.ts"() {
21
26
  "use strict";
22
27
  ACTIVE_DRONE_STATUSES = /* @__PURE__ */ new Set(["running", "blocked"]);
28
+ FILE_HOLDING_STATUSES = /* @__PURE__ */ new Set([...ACTIVE_DRONE_STATUSES, "review"]);
23
29
  EVENT_KINDS = [
24
30
  "spawned",
25
31
  "step",
@@ -303,52 +309,6 @@ var init_timeline = __esm({
303
309
  }
304
310
  });
305
311
 
306
- // ../swarm-core/src/reviewPhase.ts
307
- function computeReviewPhase(input) {
308
- const { reviewVerdict, ciRed } = input;
309
- if (reviewVerdict === "approve" && !ciRed) {
310
- return { kind: "ready", label: REVIEW_PHASE_TEXT.ready, ready: true };
311
- }
312
- if (ciRed) {
313
- const jobs = (input.ciFailing ?? []).filter((j) => j && j !== "?");
314
- const label3 = jobs.length ? `${REVIEW_PHASE_TEXT.ci_fixing}: ${jobs.join(", ")}` : REVIEW_PHASE_TEXT.ci_fixing;
315
- return { kind: "ci_fixing", label: label3, ready: false };
316
- }
317
- if (reviewVerdict === "changes") {
318
- return { kind: "changes", label: REVIEW_PHASE_TEXT.changes, ready: false };
319
- }
320
- if (input.reviewerName) {
321
- return { kind: "in_review", label: `\u041D\u0430 \u0440\u0435\u0432\u044C\u044E \u0443 ${input.reviewerName}`, ready: false };
322
- }
323
- if (input.reviewerDead) {
324
- return { kind: "reviewer_dead", label: REVIEW_PHASE_TEXT.reviewer_dead, ready: true };
325
- }
326
- const p = input.ciProgress;
327
- if (p && p.total > 0 && p.pass >= p.total) {
328
- return { kind: "awaiting_reviewer", label: REVIEW_PHASE_TEXT.awaiting_reviewer, ready: false };
329
- }
330
- const label2 = p && p.total > 0 ? `${REVIEW_PHASE_TEXT.checks} \u2014 CI ${p.pass}/${p.total}` : REVIEW_PHASE_TEXT.checks;
331
- return { kind: "checks", label: label2, ready: false };
332
- }
333
- var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
334
- var init_reviewPhase = __esm({
335
- "../swarm-core/src/reviewPhase.ts"() {
336
- "use strict";
337
- REVIEW_PHASE_TEXT = {
338
- checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
339
- awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
340
- ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
341
- in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
342
- changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
343
- ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
344
- reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
345
- };
346
- PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
347
- (a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
348
- );
349
- }
350
- });
351
-
352
312
  // ../swarm-core/src/verifyVerdict.ts
353
313
  function isVerifyVerdict(v) {
354
314
  return typeof v === "string" && VERIFY_VERDICT_SET.has(v);
@@ -396,6 +356,68 @@ var init_verifyVerdict = __esm({
396
356
  }
397
357
  });
398
358
 
359
+ // ../swarm-core/src/reviewPhase.ts
360
+ function computeReviewPhase(input) {
361
+ const { reviewVerdict, ciRed } = input;
362
+ const verifyVerdict = input.verifyVerdict ?? null;
363
+ if (!ciRed && isNegativeVerifyVerdict(verifyVerdict)) {
364
+ const kind = verifyVerdict === "found_issues" ? "verify_found" : "verify_not_reproduced";
365
+ return { kind, label: REVIEW_PHASE_TEXT[kind], ready: false };
366
+ }
367
+ if (reviewVerdict === "approve" && !ciRed) {
368
+ if (verifyVerdict === "confirmed" || input.verifyExpected === false) {
369
+ return { kind: "ready", label: REVIEW_PHASE_TEXT.ready, ready: true };
370
+ }
371
+ if (input.verifierDead) {
372
+ return { kind: "verify_dead", label: REVIEW_PHASE_TEXT.verify_dead, ready: true };
373
+ }
374
+ return { kind: "awaiting_verify", label: REVIEW_PHASE_TEXT.awaiting_verify, ready: false };
375
+ }
376
+ if (ciRed) {
377
+ const jobs = (input.ciFailing ?? []).filter((j) => j && j !== "?");
378
+ const label3 = jobs.length ? `${REVIEW_PHASE_TEXT.ci_fixing}: ${jobs.join(", ")}` : REVIEW_PHASE_TEXT.ci_fixing;
379
+ return { kind: "ci_fixing", label: label3, ready: false };
380
+ }
381
+ if (reviewVerdict === "changes") {
382
+ return { kind: "changes", label: REVIEW_PHASE_TEXT.changes, ready: false };
383
+ }
384
+ if (input.reviewerName) {
385
+ return { kind: "in_review", label: `\u041D\u0430 \u0440\u0435\u0432\u044C\u044E \u0443 ${input.reviewerName}`, ready: false };
386
+ }
387
+ if (input.reviewerDead) {
388
+ return { kind: "reviewer_dead", label: REVIEW_PHASE_TEXT.reviewer_dead, ready: true };
389
+ }
390
+ const p = input.ciProgress;
391
+ if (p && p.total > 0 && p.pass >= p.total) {
392
+ return { kind: "awaiting_reviewer", label: REVIEW_PHASE_TEXT.awaiting_reviewer, ready: false };
393
+ }
394
+ const label2 = p && p.total > 0 ? `${REVIEW_PHASE_TEXT.checks} \u2014 CI ${p.pass}/${p.total}` : REVIEW_PHASE_TEXT.checks;
395
+ return { kind: "checks", label: label2, ready: false };
396
+ }
397
+ var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
398
+ var init_reviewPhase = __esm({
399
+ "../swarm-core/src/reviewPhase.ts"() {
400
+ "use strict";
401
+ init_verifyVerdict();
402
+ REVIEW_PHASE_TEXT = {
403
+ checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
404
+ awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
405
+ ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
406
+ in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
407
+ changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
408
+ verify_found: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0430\u0448\u0451\u043B \u2014 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438",
409
+ verify_not_reproduced: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0451\u043B \u2014 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438",
410
+ awaiting_verify: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u043C \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044E",
411
+ verify_dead: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0434\u0430\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
412
+ ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
413
+ reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
414
+ };
415
+ PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
416
+ (a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
417
+ );
418
+ }
419
+ });
420
+
399
421
  // ../swarm-core/src/mergeGate.ts
400
422
  function computeMergeGate(input) {
401
423
  if (!input.hasRun) {
@@ -575,6 +597,36 @@ var init_prRef = __esm({
575
597
  }
576
598
  });
577
599
 
600
+ // ../swarm-core/src/returnReason.ts
601
+ function isReturnReason(v) {
602
+ return typeof v === "string" && RETURN_REASON_SET.has(v);
603
+ }
604
+ function toReturnReason(v) {
605
+ return isReturnReason(v) ? v : null;
606
+ }
607
+ function returnReasonLabel(r) {
608
+ switch (r) {
609
+ case "unclear_goal":
610
+ return "\u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C";
611
+ case "no_acceptance":
612
+ return "\u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442";
613
+ case "too_big":
614
+ return "\u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0432\u0435\u043B\u0438\u043A\u0430 \u0434\u043B\u044F \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430";
615
+ case "contradiction":
616
+ return "\u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u0441\u0430\u043C\u0430 \u0441\u0435\u0431\u0435";
617
+ }
618
+ }
619
+ var RETURN_REASONS, RETURN_REASON_SET, UNRETURNED_FAILURE_STATUSES, UNRETURNED_FAILURE_SET;
620
+ var init_returnReason = __esm({
621
+ "../swarm-core/src/returnReason.ts"() {
622
+ "use strict";
623
+ RETURN_REASONS = ["unclear_goal", "no_acceptance", "too_big", "contradiction"];
624
+ RETURN_REASON_SET = new Set(RETURN_REASONS);
625
+ UNRETURNED_FAILURE_STATUSES = ["failed", "blocked"];
626
+ UNRETURNED_FAILURE_SET = new Set(UNRETURNED_FAILURE_STATUSES);
627
+ }
628
+ });
629
+
578
630
  // ../swarm-core/src/modelPolicy.ts
579
631
  function findProvider(id) {
580
632
  const key = typeof id === "string" ? id.trim().toLowerCase() : "";
@@ -1673,6 +1725,57 @@ var init_zoneMemory = __esm({
1673
1725
  }
1674
1726
  });
1675
1727
 
1728
+ // ../swarm-core/src/taskPrs.ts
1729
+ function normalizePrUrl(raw) {
1730
+ const s = (raw ?? "").trim();
1731
+ if (!s) return null;
1732
+ const m = s.match(/^(?:https?:\/\/)?(?:www\.)?github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/(\d+)/i);
1733
+ if (!m) return s.replace(/\/+$/, "").toLowerCase();
1734
+ return `github.com/${m[1]}/${m[2]}/pull/${m[3]}`.toLowerCase();
1735
+ }
1736
+ function samePr(a, b) {
1737
+ const x = normalizePrUrl(a);
1738
+ const y = normalizePrUrl(b);
1739
+ return x != null && y != null && x === y;
1740
+ }
1741
+ function prBelongsToTask(slug, pr) {
1742
+ const s = (slug ?? "").trim().toLowerCase();
1743
+ if (!s) return false;
1744
+ const branch = (pr.headRefName ?? "").trim().toLowerCase();
1745
+ if (branch === s || branch.startsWith(s) && /^[-/_.]/.test(branch.slice(s.length))) return true;
1746
+ const title = (pr.title ?? "").toLowerCase();
1747
+ if (!title.includes(s)) return false;
1748
+ const at = title.indexOf(s);
1749
+ const before = at > 0 ? title[at - 1] : "";
1750
+ const after = title[at + s.length] ?? "";
1751
+ return !/[a-z0-9]/.test(before) && !/[a-z0-9-]/.test(after);
1752
+ }
1753
+ function decideTaskWork(facts) {
1754
+ const candidate = (facts.mergedCandidate ?? "").trim() || null;
1755
+ const open = [];
1756
+ for (const raw of facts.openPrs ?? []) {
1757
+ const url = (raw ?? "").trim();
1758
+ if (!url) continue;
1759
+ if (!open.some((u) => samePr(u, url))) open.push(url);
1760
+ }
1761
+ if (open.length > 0) return { kind: "open", prs: open, candidate: candidate && !open.some((u) => samePr(u, candidate)) ? candidate : null };
1762
+ if (!candidate) return { kind: "unmerged" };
1763
+ return { kind: "merged", pr: candidate };
1764
+ }
1765
+ function formatMergedDuplicateReason(pr) {
1766
+ return `PR \u0437\u0430\u0434\u0430\u0447\u0438 \u0443\u0436\u0435 \u0441\u043C\u0435\u0440\u0436\u0435\u043D (${pr}) \u2014 \u043F\u0440\u043E\u0433\u043E\u043D-\u0434\u0443\u0431\u043B\u044C \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u0441\u043F\u0430\u0432\u043D\u0430/\u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0438 (TASWM-206)`;
1767
+ }
1768
+ function formatOpenPrHold(v) {
1769
+ const list = v.prs.join(", ");
1770
+ const tail2 = v.candidate ? ` (\u0441\u043C\u0435\u0440\u0436\u0435\u043D ${v.candidate}, \u043D\u043E \u044D\u0442\u043E \u043D\u0435 \u0432\u0441\u044F \u0440\u0430\u0431\u043E\u0442\u0430)` : "";
1771
+ return `\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u043E\u0441\u0442\u0430\u043B\u0438\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 PR: ${list}${tail2} \u2014 \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0432\u043B\u0438\u0442\u0430 \u0446\u0435\u043B\u0438\u043A\u043E\u043C, \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0435\u043C \u0438 \u043F\u0440\u043E\u0433\u043E\u043D \u0434\u0443\u0431\u043B\u0435\u043C \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u043C (TASWM-515)`;
1772
+ }
1773
+ var init_taskPrs = __esm({
1774
+ "../swarm-core/src/taskPrs.ts"() {
1775
+ "use strict";
1776
+ }
1777
+ });
1778
+
1676
1779
  // ../swarm-core/src/index.ts
1677
1780
  var init_src = __esm({
1678
1781
  "../swarm-core/src/index.ts"() {
@@ -1684,6 +1787,7 @@ var init_src = __esm({
1684
1787
  init_mergeQueue();
1685
1788
  init_prRef();
1686
1789
  init_verifyVerdict();
1790
+ init_returnReason();
1687
1791
  init_modelPolicy();
1688
1792
  init_status();
1689
1793
  init_deps();
@@ -1706,6 +1810,7 @@ var init_src = __esm({
1706
1810
  init_effort();
1707
1811
  init_queuePlan();
1708
1812
  init_zoneMemory();
1813
+ init_taskPrs();
1709
1814
  }
1710
1815
  });
1711
1816
 
@@ -3962,6 +4067,12 @@ function invalidPatchValue(field, v) {
3962
4067
  return typeof v === "number" && Number.isInteger(v) && v >= 0 ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0446\u0438\u043A\u043B \u0440\u0435\u0432\u044C\u044E: ${String(v)}`;
3963
4068
  case "pr_url":
3964
4069
  return v === null || typeof v === "string" ? null : "pr_url \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u043E\u0439";
4070
+ case "changed_paths": {
4071
+ if (!Array.isArray(v)) return "changed_paths \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0430\u0441\u0441\u0438\u0432\u043E\u043C \u043F\u0443\u0442\u0435\u0439";
4072
+ if (v.length > CHANGED_PATHS_MAX) return `changed_paths: \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u043D\u043E\u0433\u043E \u043F\u0443\u0442\u0435\u0439 (${v.length} > ${CHANGED_PATHS_MAX})`;
4073
+ const badPath = v.find((p) => typeof p !== "string" || !p || p.length > CHANGED_PATH_LEN_MAX);
4074
+ return badPath === void 0 ? null : `changed_paths: \u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u043F\u0443\u0442\u044C: ${String(badPath).slice(0, 80)}`;
4075
+ }
3965
4076
  default:
3966
4077
  return `\u043F\u043E\u043B\u0435 \xAB${field}\xBB \u043A\u0430\u043D\u0430\u043B\u0443 \u0434\u0440\u043E\u043D\u0430 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442\u0441\u044F`;
3967
4078
  }
@@ -4215,7 +4326,7 @@ function channelDroneStore(opts) {
4215
4326
  openQuestionsFor: async () => (await call("openQuestionsFor")).questions
4216
4327
  };
4217
4328
  }
4218
- var LOCAL_CLI_KEY, PATCHABLE, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
4329
+ var LOCAL_CLI_KEY, PATCHABLE, CHANGED_PATHS_MAX, CHANGED_PATH_LEN_MAX, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
4219
4330
  var init_droneChannel = __esm({
4220
4331
  "src/droneChannel.ts"() {
4221
4332
  "use strict";
@@ -4229,8 +4340,16 @@ var init_droneChannel = __esm({
4229
4340
  // и пишется он тем же путём на ПРОВЕРЯЕМЫЙ прогон. Без него роль verifier была немой:
4230
4341
  // вердикт выносился комментарием на PR, а `done` отбивался каналом.
4231
4342
  "verify_verdict",
4232
- "review_cycle"
4343
+ "review_cycle",
4344
+ // TASWM-506: файлы, которые прогон занял своим PR. Едут тем же вызовом, что и сдача
4345
+ // (done → status=review + pr_url): считает их НАШ код в дрон-сервере (`changedPaths` по
4346
+ // worktree), а не модель — со стороны канала это такой же самоотчёт прогона о себе, как
4347
+ // pr_url. Потолки на размер — ниже, в invalidPatchValue: список идёт в гейт раздачи, и
4348
+ // сорвавшийся дрон не должен уметь заявить занятым весь репозиторий.
4349
+ "changed_paths"
4233
4350
  ]);
4351
+ CHANGED_PATHS_MAX = 1e3;
4352
+ CHANGED_PATH_LEN_MAX = 1024;
4234
4353
  EVENT_KIND_SET = new Set(EVENT_KINDS);
4235
4354
  ChannelUnavailableError = class extends Error {
4236
4355
  constructor(downMs, message) {
@@ -5541,9 +5660,14 @@ function rowToRun(row) {
5541
5660
  tokens_out: row.tokens_out ?? 0,
5542
5661
  duration_ms: row.duration_ms ?? 0,
5543
5662
  pr_url: row.pr_url ?? null,
5663
+ // TASWM-506: файлы, занятые прогоном с открытым PR — JSON-массив, как depends_on.
5664
+ changed_paths: row.changed_paths ? JSON.parse(row.changed_paths) : [],
5544
5665
  superseded_by: row.superseded_by ?? null,
5545
5666
  decision: row.decision ? JSON.parse(row.decision) : null,
5546
5667
  stop_reason: row.stop_reason ?? null,
5668
+ // TASWM-505: через узкий парсер, как verify_verdict ниже — в базе может лежать код,
5669
+ // записанный демоном другой версии; непонятное здесь значит «кода нет».
5670
+ return_reason: toReturnReason(row.return_reason),
5547
5671
  respawns: row.respawns ?? 0,
5548
5672
  // старые прогоны без колонки → 0
5549
5673
  resolves_run_id: row.resolves_run_id ?? null,
@@ -5618,6 +5742,7 @@ CREATE TABLE IF NOT EXISTS runs (
5618
5742
  superseded_by INTEGER,
5619
5743
  decision TEXT,
5620
5744
  stop_reason TEXT,
5745
+ return_reason TEXT,
5621
5746
  respawns INTEGER NOT NULL DEFAULT 0,
5622
5747
  resolves_run_id INTEGER,
5623
5748
  reviews_run_id INTEGER,
@@ -5734,7 +5859,16 @@ CREATE TABLE IF NOT EXISTS imports (
5734
5859
  // TASWM-451: чем проверять работу этого прогона — модель ревью, приехавшая с задачи.
5735
5860
  // Живёт на прогоне ИСПОЛНИТЕЛЯ: ревьюер и верификатор заводятся локально и читают её
5736
5861
  // оттуда, а не ходят за задачей в прод в момент спавна.
5737
- "ALTER TABLE runs ADD COLUMN review_model TEXT"
5862
+ "ALTER TABLE runs ADD COLUMN review_model TEXT",
5863
+ // TASWM-505: КОД причины возврата задачи автору (unclear_goal|no_acceptance|too_big|
5864
+ // contradiction) рядом с прозой в stop_reason — из него складывается распределение
5865
+ // «чего в постановках не хватает чаще всего». NULL — прогон не возвращали либо
5866
+ // возврат сделан демоном до этой фичи.
5867
+ "ALTER TABLE runs ADD COLUMN return_reason TEXT",
5868
+ // TASWM-506: файлы, которые держит прогон с открытым PR (JSON-массив, как depends_on).
5869
+ // Снимается в момент сдачи с PR: worktree сносится на мерже, и пересчитать список
5870
+ // потом неоткуда.
5871
+ "ALTER TABLE runs ADD COLUMN changed_paths TEXT"
5738
5872
  ]) {
5739
5873
  try {
5740
5874
  this.db.exec(alter);
@@ -5880,7 +6014,7 @@ CREATE TABLE IF NOT EXISTS imports (
5880
6014
  const set = fields.map((f) => `${f} = ?`).join(", ");
5881
6015
  const vals = fields.map((f) => {
5882
6016
  const v = patch[f];
5883
- if (f === "depends_on") {
6017
+ if (f === "depends_on" || f === "changed_paths") {
5884
6018
  const arr = v;
5885
6019
  return arr && arr.length ? JSON.stringify(arr) : null;
5886
6020
  }
@@ -7088,9 +7222,11 @@ var SwarmClient = class _SwarmClient {
7088
7222
  if ("duration_ms" in patch && patch.duration_ms !== void 0) out.durationMs = patch.duration_ms;
7089
7223
  if ("respawns" in patch && patch.respawns !== void 0) out.respawns = patch.respawns;
7090
7224
  if ("pr_url" in patch) out.prUrl = patch.pr_url ?? null;
7225
+ if ("changed_paths" in patch && patch.changed_paths) out.changedPaths = patch.changed_paths;
7091
7226
  if ("branch" in patch) out.branch = patch.branch ?? null;
7092
7227
  if ("repo" in patch) out.repo = patch.repo ?? null;
7093
7228
  if ("stop_reason" in patch) out.stopReason = patch.stop_reason ?? null;
7229
+ if ("return_reason" in patch) out.returnReason = patch.return_reason ?? null;
7094
7230
  if ("depends_on" in patch && patch.depends_on) out.dependsOn = patch.depends_on;
7095
7231
  if ("decision" in patch) out.decision = patch.decision ? JSON.stringify(patch.decision) : null;
7096
7232
  if ("kind" in patch && patch.kind !== void 0) out.role = patch.kind;
@@ -7166,9 +7302,12 @@ var SwarmClient = class _SwarmClient {
7166
7302
  tokens_out: 0,
7167
7303
  duration_ms: 0,
7168
7304
  pr_url: null,
7305
+ changed_paths: [],
7306
+ // файлы прогона снимаются при открытии PR (TASWM-506)
7169
7307
  superseded_by: null,
7170
7308
  decision: input.decision ?? null,
7171
7309
  stop_reason: null,
7310
+ return_reason: null,
7172
7311
  respawns: 0,
7173
7312
  resolves_run_id: input.resolves_run_id ?? null,
7174
7313
  reviews_run_id: input.reviews_run_id ?? null,
@@ -8770,9 +8909,12 @@ var SwarmClient = class _SwarmClient {
8770
8909
  tokens_out: 0,
8771
8910
  duration_ms: 0,
8772
8911
  pr_url: null,
8912
+ changed_paths: [],
8913
+ // занятые файлы (TASWM-506) — прод-поле, заполняется ниже
8773
8914
  superseded_by: null,
8774
8915
  decision: null,
8775
8916
  stop_reason: null,
8917
+ return_reason: null,
8776
8918
  respawns: 0,
8777
8919
  resolves_run_id: null,
8778
8920
  reviews_run_id: null,
@@ -8828,6 +8970,7 @@ var SwarmClient = class _SwarmClient {
8828
8970
  const verProd = pr.verifiesRunId ?? null;
8829
8971
  if (verProd) local.verifies_run_id = this.localIdForProdId(verProd) ?? local.verifies_run_id;
8830
8972
  local.verify_verdict = toVerifyVerdict(pr.verifyVerdict) ?? local.verify_verdict;
8973
+ local.return_reason = toReturnReason(pr.returnReason) ?? local.return_reason;
8831
8974
  local.review_phase = pr.reviewPhase ?? local.review_phase;
8832
8975
  local.repo = this.resolveRepo(pr.repo ?? null);
8833
8976
  local.host_id = pr.hostId ?? null;
@@ -8847,6 +8990,7 @@ var SwarmClient = class _SwarmClient {
8847
8990
  local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
8848
8991
  local.duration_ms = typeof pr.durationMs === "number" ? pr.durationMs : 0;
8849
8992
  local.pr_url = pr.prUrl ?? null;
8993
+ local.changed_paths = Array.isArray(pr.changedPaths) ? pr.changedPaths : local.changed_paths ?? [];
8850
8994
  local.stop_reason = pr.stopReason ?? null;
8851
8995
  local.respawns = typeof pr.respawns === "number" ? pr.respawns : 0;
8852
8996
  local.decision = parseDecision(pr.decision);
@@ -11048,25 +11192,76 @@ var UNRETRIABLE_PARK_MARK = "\u043F\u043E\u0432\u0442\u043E\u0440 \u043D\u0435 \
11048
11192
  function isUnretriableParked(run) {
11049
11193
  return isPreStartFailure(run) && (run.stop_reason ?? "").includes(UNRETRIABLE_PARK_MARK);
11050
11194
  }
11195
+ var PARK_APPLIED_MARK = "\u0437\u0430\u0434\u0430\u0447\u0430 \u0441\u043D\u044F\u0442\u0430 \u0441 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0440\u043E\u044F";
11196
+ var PARK_APPLIED_RE = new RegExp(`${PARK_APPLIED_MARK} \\(([^,)]+)(?:, \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043F\u043E #(\\d+))?\\)`);
11197
+ function withParkApplied(reason, at, upToRunId) {
11198
+ if (PARK_APPLIED_RE.test(reason)) return reason;
11199
+ const upTo = upToRunId != null ? `, \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043F\u043E #${upToRunId}` : "";
11200
+ return `${reason} \u2014 ${PARK_APPLIED_MARK} (${at}${upTo})`;
11201
+ }
11202
+ function parkApplied(run) {
11203
+ if (!run || !isParkApplied(run)) return null;
11204
+ const m = PARK_APPLIED_RE.exec(run.stop_reason ?? "");
11205
+ if (!m) return null;
11206
+ const ms = m[1] ? stampMs(m[1]) : Number.NaN;
11207
+ return {
11208
+ at: Number.isFinite(ms) ? ms : null,
11209
+ upToRunId: m[2] ? Number(m[2]) : null
11210
+ };
11211
+ }
11212
+ function lastRunIdFor(runs, taskSlug) {
11213
+ return ownRuns(runs, taskSlug).reduce((max, r) => Math.max(max, r.id), 0);
11214
+ }
11215
+ function isParkApplied(run) {
11216
+ return isPreStartFailure(run) && (run.stop_reason ?? "").includes(PARK_APPLIED_MARK);
11217
+ }
11218
+ var PARK_CAUSE_PREFIX = "\u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E: ";
11219
+ function parkedCause(run) {
11220
+ const at = (run?.stop_reason ?? "").indexOf(PARK_CAUSE_PREFIX);
11221
+ if (at < 0) return null;
11222
+ const cause = run.stop_reason.slice(at + PARK_CAUSE_PREFIX.length).replace(new RegExp(`\\s*\u2014\\s*${PARK_APPLIED_MARK} \\([^)]*\\)\\s*$`), "").trim();
11223
+ return cause || null;
11224
+ }
11051
11225
  var SPAWN_ATTEMPT_WINDOW_MS = 60 * 6e4;
11226
+ function ownRuns(runs, taskSlug) {
11227
+ return runs.filter(
11228
+ (r) => r.task_slug === taskSlug && r.reviews_run_id == null && r.resolves_run_id == null && r.verifies_run_id == null
11229
+ );
11230
+ }
11231
+ function newestFirst(a, b) {
11232
+ const at = a.created_at ? stampMs(a.created_at) : Number.NaN;
11233
+ const bt = b.created_at ? stampMs(b.created_at) : Number.NaN;
11234
+ if (Number.isFinite(at) && Number.isFinite(bt) && at !== bt) return bt - at;
11235
+ return b.id - a.id;
11236
+ }
11052
11237
  function preStartAttempts(runs, taskSlug, opts = {}) {
11053
11238
  const windowMs = opts.windowMs ?? SPAWN_ATTEMPT_WINDOW_MS;
11054
11239
  const now = opts.now ?? Date.now();
11055
- const own = runs.filter((r) => r.task_slug === taskSlug && r.reviews_run_id == null && r.resolves_run_id == null && r.verifies_run_id == null).sort((a, b) => b.id - a.id);
11240
+ const own = ownRuns(runs, taskSlug).sort(newestFirst);
11056
11241
  let n = 0;
11242
+ let newestCreatedAt = Number.NEGATIVE_INFINITY;
11243
+ let newestSeenId = Number.NEGATIVE_INFINITY;
11057
11244
  for (const run of own) {
11245
+ const applied = parkApplied(run);
11246
+ if (applied) {
11247
+ const returned = applied.upToRunId != null ? newestSeenId > applied.upToRunId : applied.at != null && newestCreatedAt > applied.at;
11248
+ if (returned) break;
11249
+ }
11250
+ newestSeenId = Math.max(newestSeenId, run.id);
11251
+ const startedAt = run.created_at ? stampMs(run.created_at) : Number.NaN;
11252
+ if (Number.isFinite(startedAt)) newestCreatedAt = Math.max(newestCreatedAt, startedAt);
11058
11253
  if (run.status === "queued" || run.status === "running") continue;
11059
11254
  if (isSpawnParked(run) && run.status !== "failed") continue;
11060
11255
  if (!isPreStartFailure(run)) break;
11061
- const at = run.created_at ? stampMs(run.created_at) : Number.NaN;
11062
- if (Number.isFinite(at) && now - at >= windowMs) break;
11256
+ if (Number.isFinite(startedAt) && now - startedAt >= windowMs) break;
11257
+ if (opts.since != null && Number.isFinite(startedAt) && startedAt < opts.since) break;
11063
11258
  n += 1;
11064
11259
  if (isUnretriableParked(run)) return Math.max(n, PRE_START_ATTEMPT_LIMIT);
11065
11260
  }
11066
11261
  return n;
11067
11262
  }
11068
11263
  function lastPreStartAttempt(runs, taskSlug) {
11069
- return runs.filter((r) => r.task_slug === taskSlug && isPreStartFailure(r)).sort((a, b) => b.id - a.id)[0] ?? null;
11264
+ return runs.filter((r) => r.task_slug === taskSlug && isPreStartFailure(r)).sort(newestFirst)[0] ?? null;
11070
11265
  }
11071
11266
  function unresolvedRepoReason(repo, served) {
11072
11267
  const raw = repo.trim();
@@ -11249,6 +11444,17 @@ async function mergedPrForBranch(branch, repoDir, run = realRepoRunner) {
11249
11444
  return null;
11250
11445
  }
11251
11446
  }
11447
+ async function openPrsForTask(slug, repoDir, run = realRepoRunner) {
11448
+ if (!slug || !repoDir) return null;
11449
+ try {
11450
+ const out = await run(["pr", "list", "--state", "open", "--json", "url,headRefName,title", "--limit", "50"], repoDir);
11451
+ const arr = JSON.parse(out);
11452
+ if (!Array.isArray(arr)) return null;
11453
+ return arr.filter((p) => typeof p.url === "string" && prBelongsToTask(slug, { headRefName: p.headRefName, title: p.title })).map((p) => p.url);
11454
+ } catch {
11455
+ return null;
11456
+ }
11457
+ }
11252
11458
  async function closePR(prUrl, run = realRunner) {
11253
11459
  await run(["pr", "close", prUrl, "--comment", "\u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043D \u043D\u0430 \u0434\u0440\u0443\u0433\u043E\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 (swarm)"]);
11254
11460
  }
@@ -11472,118 +11678,319 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11472
11678
  import { z } from "zod";
11473
11679
  init_git();
11474
11680
 
11475
- // src/zoneNotes.ts
11476
- init_src();
11477
- function parseZoneMemory(raw, zone) {
11478
- const key = normalizeZoneLabel(zone);
11479
- if (!key || !raw || typeof raw !== "object" || Array.isArray(raw)) return [];
11480
- const notes = raw[key];
11481
- if (!Array.isArray(notes)) return [];
11482
- const out = [];
11483
- for (const item of notes) {
11484
- if (!item || typeof item !== "object") continue;
11485
- const n = item;
11486
- const id = typeof n.id === "string" ? n.id : "";
11487
- const claim = typeof n.claim === "string" ? n.claim.trim() : "";
11488
- if (id && claim) out.push({ id, claim });
11489
- }
11490
- return out;
11491
- }
11492
- function normalizeZoneLabel(zone) {
11493
- return (zone ?? "").trim().replace(/^area:/i, "").trim().toLowerCase();
11494
- }
11495
- function renderZoneMemoryBlock(zone, notes) {
11496
- const label2 = normalizeZoneLabel(zone);
11497
- if (!label2) return "";
11498
- const head = notes.length ? [
11499
- `\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u2014 \u0447\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u0432\u044B\u044F\u0441\u043D\u0438\u043B \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 (\u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u043E\u043C, \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u043A\u043E\u0434\u0430):`,
11500
- ...notes.map((n) => `\u2022 [${n.id}] ${n.claim}`),
11501
- "\u042D\u0442\u043E \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438, \u0430 \u043D\u0435 \u0438\u0441\u0442\u0438\u043D\u0430 \u0432 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0439 \u0438\u043D\u0441\u0442\u0430\u043D\u0446\u0438\u0438: \u0440\u0430\u0437\u043E\u0448\u043B\u043E\u0441\u044C \u0441 \u043A\u043E\u0434\u043E\u043C \u2014 \u0432\u0435\u0440\u044C \u043A\u043E\u0434\u0443 \u0438 \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u0441\u0434\u0430\u0447\u0435.",
11502
- `\u041F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u0430\u0441\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u0451 \u0432 done: \`zone_notes_used: ["${notes[0].id}"]\` (id \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0432\u044B\u0448\u0435 \u0438\u043B\u0438 \u0441\u0430\u043C \u0442\u0435\u043A\u0441\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F). \u042D\u0442\u043E \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0435, \u0447\u0435\u043C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0436\u0438\u0432\u0451\u0442: \u043F\u0430\u043C\u044F\u0442\u044C \u0437\u043E\u043D\u044B \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430, \u0438 \u0432\u044B\u0442\u0435\u0441\u043D\u044F\u0435\u0442\u0441\u044F \u0442\u0430, \u0432 \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0440\u043E\u0439 \u0434\u043E\u043B\u044C\u0448\u0435 \u0432\u0441\u0435\u0445 \u043D\u0435 \u0443\u0431\u0435\u0436\u0434\u0430\u043B\u0441\u044F. \u041D\u0435 \u043F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u043E\u0441\u044C \u043D\u0438\u0447\u0435\u0433\u043E \u2014 \u043D\u0435 \u043D\u0430\u0437\u044B\u0432\u0430\u0439 \u043D\u0438\u0447\u0435\u0433\u043E, \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434.`
11503
- ] : [`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u043F\u0443\u0441\u0442\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B.`];
11504
- return [
11505
- ...head,
11506
- `\u{1F9E0} \u0417\u0410\u041C\u0415\u0422\u041A\u0410 \u0412 \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \u2014 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0434\u043E ${ZONE_NOTES_PER_RUN_MAX} \u0448\u0442\u0443\u043A: \`zone_notes: [{ zone: "${label2}", claim: "\u2026", why: "\u2026" }]\` (zone \u043C\u043E\u0436\u043D\u043E \u043D\u0435 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u2014 \u0432\u043E\u0437\u044C\u043C\u0451\u0442\u0441\u044F \u0442\u0432\u043E\u044F). \u0427\u0422\u041E \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 (\xAB\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0447\u0438\u0442\u0430\u044E\u0442 \u0438\u0437 \u043E\u0434\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u2026\xBB), \u0433\u0440\u0430\u0431\u043B\u0438 \u0441 \u0446\u0435\u043D\u043E\u0439 (\xAB\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043D\u0430\u043A\u0430\u0442\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u0442\u0435\u043C, \u0447\u0435\u043C \u043A\u0430\u0436\u0435\u0442\u0441\u044F, \u2014 \u0441\u0442\u043E\u0438\u043B\u043E \u0447\u0430\u0441\u0430\xBB), \u043E\u0442\u0431\u0440\u043E\u0448\u0435\u043D\u043D\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439. \u0427\u0422\u041E \u041D\u0415 \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043A\u043E\u0434\u0430 (\xAB\u0445\u0435\u043D\u0434\u043B\u0435\u0440 \u043B\u0435\u0436\u0438\u0442 \u0432 handlers.ts\xBB, \xAB\u044D\u0442\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F \u0434\u0435\u043B\u0430\u0435\u0442 \u0442\u043E-\u0442\u043E\xBB) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0435\u0442 \u044D\u0442\u043E \u0441\u0430\u043C \u0438 \u0431\u044B\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043C \u0442\u0432\u043E\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u043D\u0438\u0447\u0435\u0433\u043E \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u043F\u0438\u0441\u043E\u043A \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434 \u043F\u0440\u043E\u0433\u043E\u043D\u0430.`
11507
- ].join("\n");
11508
- }
11509
- function collectZoneNoteConfirmations(report) {
11510
- const seen = /* @__PURE__ */ new Set();
11511
- const out = [];
11512
- for (const raw of report.used ?? []) {
11513
- const s = typeof raw === "string" ? raw.trim() : "";
11514
- if (!s || seen.has(s)) continue;
11515
- seen.add(s);
11516
- out.push(s);
11517
- }
11518
- return out;
11519
- }
11520
-
11521
- // src/droneServer.ts
11522
- init_src();
11523
-
11524
- // src/reviewCycle.ts
11525
- init_src();
11526
- function needsFixIteration(run) {
11527
- return run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict));
11681
+ // src/diff.ts
11682
+ import { statSync as statSync10 } from "fs";
11683
+ import { join as join25 } from "path";
11684
+ import { $ as $4 } from "execa";
11685
+ var MAX_FILES = 200;
11686
+ var MAX_LINES_PER_FILE = 1500;
11687
+ var MAX_UNTRACKED = 60;
11688
+ function cleanPath(raw) {
11689
+ let p = raw.trim();
11690
+ if (p.startsWith('"') && p.endsWith('"') && p.length >= 2) p = p.slice(1, -1);
11691
+ return p;
11528
11692
  }
11529
- function reviewTransitionPatch(run, prUrl) {
11530
- const isFixIteration = needsFixIteration(run);
11531
- if (isFixIteration) {
11532
- const cycle = (run.review_cycle ?? 0) + 1;
11533
- return {
11534
- isFixIteration: true,
11535
- cycle,
11536
- patch: { status: "review", pr_url: prUrl, review_cycle: cycle, review_verdict: null, verify_verdict: null }
11537
- };
11538
- }
11539
- return { isFixIteration: false, cycle: null, patch: { status: "review", pr_url: prUrl } };
11693
+ function stripAB(raw) {
11694
+ const p = cleanPath(raw);
11695
+ if (p === "/dev/null") return p;
11696
+ if (p.startsWith("a/") || p.startsWith("b/")) return p.slice(2);
11697
+ return p;
11540
11698
  }
11541
- function branchFromPrPatch(currentBranch, headBranch) {
11542
- if (!headBranch || headBranch === currentBranch) return null;
11543
- return {
11544
- patch: { branch: headBranch },
11545
- event: currentBranch ? `\u0432\u0435\u0442\u043A\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0432\u0437\u044F\u0442\u0430 \u0443 PR: ${currentBranch} \u2192 ${headBranch} (\u043F\u043E\u043B\u0435 \u0431\u044B\u043B\u043E \u0441\u043E\u0431\u0440\u0430\u043D\u043E \u0438\u0437 \u0441\u043B\u0430\u0433\u0430 \u2014 TASWM-356)` : `\u0432\u0435\u0442\u043A\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0432\u0437\u044F\u0442\u0430 \u0443 PR: ${headBranch} (TASWM-356)`
11699
+ function parseUnifiedDiff(text) {
11700
+ const files = [];
11701
+ if (!text) return files;
11702
+ const lines = text.split("\n");
11703
+ let f = null;
11704
+ let hunk = null;
11705
+ let oldNo = 0;
11706
+ let newNo = 0;
11707
+ const push = () => {
11708
+ if (f) files.push(f);
11546
11709
  };
11547
- }
11548
- function prLiveness(st) {
11549
- if (!st) return "unknown";
11550
- if (st.merged) return "merged";
11551
- const state = (st.state ?? "").toUpperCase();
11552
- if (state === "MERGED") return "merged";
11553
- if (state === "CLOSED") return "closed";
11554
- if (state === "UNKNOWN") return "unknown";
11555
- return "open";
11556
- }
11557
- function applyReviewTransition(db, run, prUrl) {
11558
- const { patch, ...t } = reviewTransitionPatch(run, prUrl);
11559
- db.updateRun(run.id, patch);
11560
- return t;
11561
- }
11562
-
11563
- // src/droneServer.ts
11564
- init_src();
11565
- function readTaskFailureInstruction(slug, failure) {
11566
- const why = describeToolFailure(failure);
11567
- if (TOOL_FAILURE_RETRIABLE[failure.code]) {
11568
- return `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 ${slug}: ${why}. \u042D\u0442\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u0438 \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E\u043A\u0430 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0436\u0435 \u0432 \u043F\u0440\u043E\u043C\u043F\u0442\u0435, \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 read_task \u0435\u0449\u0451 \u0440\u0430\u0437 \u043F\u043E\u0437\u0436\u0435 (\u043F\u0435\u0440\u0435\u0434 \u0441\u0434\u0430\u0447\u0435\u0439 \u2014 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E).`;
11569
- }
11570
- return [
11571
- `\u26D4 \u0417\u0410\u0414\u0410\u0427\u0423 ${slug} \u041F\u0420\u041E\u0427\u0418\u0422\u0410\u0422\u042C \u041D\u0415\u041B\u042C\u0417\u042F: ${why}.`,
11572
- "\u041F\u043E\u0432\u0442\u043E\u0440 \u0442\u0443\u0442 \u043D\u0435 \u043F\u043E\u043C\u043E\u0436\u0435\u0442 \u2014 \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043D\u0435 \u0432 \u0441\u0435\u0442\u0438, \u043E\u043D\u0430 \u0441\u0430\u043C\u0430 \u043D\u0435 \u0440\u0430\u0437\u043E\u0439\u0434\u0451\u0442\u0441\u044F.",
11573
- "\u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E \u0441\u043B\u0430\u0433\u0443 \u0438 \u043F\u043E \u043F\u0430\u043C\u044F\u0442\u0438: \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0442\u044B \u043D\u0435 \u0432\u0438\u0434\u0435\u043B, \u043D\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0438\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 (TASWM-288).",
11574
- "\u0421\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0447\u0435\u0440\u0435\u0437 ask() \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u043C\u0443 \u044D\u0442\u0443 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0434\u043E\u0441\u043B\u043E\u0432\u043D\u043E \u2014 \u0438 \u0434\u0430\u043B\u044C\u0448\u0435 \u0434\u0435\u043B\u0430\u0439 \u0442\u043E, \u0447\u0442\u043E \u043E\u043D \u043E\u0442\u0432\u0435\u0442\u0438\u0442."
11575
- ].join("\n");
11576
- }
11577
- function freezeInstruction(branch, reason = "\u0435\u0433\u043E \u043F\u0443\u043B\u044C\u0441 (heartbeat) \u0443\u0441\u0442\u0430\u0440\u0435\u043B") {
11578
- return [
11579
- `\u26D4 OVERMIND \u041D\u0415\u0414\u041E\u0421\u0422\u0423\u041F\u0415\u041D \u2014 ${reason}. \u041D\u0430\u0434\u0437\u043E\u0440\u0430 \u043D\u0430\u0434 \u0442\u043E\u0431\u043E\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442: \u043D\u0435\u043A\u043E\u043C\u0443`,
11580
- "\u043F\u0440\u0438\u043D\u044F\u0442\u044C \u0442\u0432\u043E\u0439 \u0432\u043E\u043F\u0440\u043E\u0441, \u0437\u0430\u043C\u0435\u0442\u0438\u0442\u044C \u0441\u043C\u0435\u0440\u0442\u044C, \u0441\u043C\u0435\u0440\u0436\u0438\u0442\u044C PR \u0438\u043B\u0438 \u0440\u0430\u0437\u0440\u0443\u043B\u0438\u0442\u044C \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442. \u0420\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u0432\u0441\u043B\u0435\u043F\u0443\u044E \u043D\u0435\u043B\u044C\u0437\u044F.",
11581
- "\u0417\u0410\u041C\u0420\u0418 \u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0441\u044C, \u0447\u0442\u043E\u0431\u044B \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u043F\u0440\u043E\u043F\u0430\u043B\u0430:",
11582
- "1. \u0417\u0430\u043A\u043E\u043C\u043C\u0438\u0442\u044C \u0412\u0421\u042E \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043A\u0430\u043A \u0435\u0441\u0442\u044C: `git add -A && git commit` (\u0442\u0440\u0435\u0439\u043B\u0435\u0440 \u043F\u0440\u043E \u0434\u0440\u043E\u043D\u0430 \u2014 \u043A\u0430\u043A \u043E\u0431\u044B\u0447\u043D\u043E).",
11583
- " \u041D\u043E\u0432\u044B\u0439 \u043A\u043E\u0434 \u041D\u0415 \u043F\u0438\u0448\u0438, \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u0430\u043B\u044C\u0448\u0435 \u041D\u0415 \u0434\u0435\u043B\u0430\u0439 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u043E\u0445\u0440\u0430\u043D\u0438 \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435, \u0431\u0435\u0437 \u043C\u0443\u0441\u043E\u0440\u043D\u044B\u0445 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432.",
11584
- `2. \u0417\u0430\u043F\u0443\u0448\u044C \u0432\u0435\u0442\u043A\u0443: \`git push -u origin ${branch}\` \u2014 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u0443\u043C\u0440\u0451\u0442 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u043C.`,
11585
- "3. \u0417\u0430\u0432\u0435\u0440\u0448\u0438 \u0440\u0430\u0431\u043E\u0442\u0443 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u041D\u0415 \u0437\u043E\u0432\u0438 (\u043D\u0438 report_step, \u043D\u0438 ask, \u043D\u0438 done). \u041F\u0440\u043E\u0441\u0442\u043E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438 \u0441\u0435\u0441\u0441\u0438\u044E.",
11586
- "\u041F\u0440\u043E\u0433\u043E\u043D \u043F\u043E\u043C\u0435\u0447\u0435\u043D \xABpaused\xBB. \u041A\u043E\u0433\u0434\u0430 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u043D\u043E\u0432\u044B\u0439 overmind, \u043E\u043D \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442 \u0442\u0435\u0431\u044F \u0441 \u044D\u0442\u043E\u0433\u043E \u043C\u0435\u0441\u0442\u0430 (--continue) \u2014",
11710
+ for (const line of lines) {
11711
+ if (line.startsWith("diff --git ")) {
11712
+ push();
11713
+ hunk = null;
11714
+ const m = line.match(/^diff --git (.+) (.+)$/);
11715
+ const guess = m ? stripAB(m[2]) : "";
11716
+ f = { path: guess, oldPath: null, status: "modified", added: 0, removed: 0, binary: false, hunks: [] };
11717
+ continue;
11718
+ }
11719
+ if (!f) continue;
11720
+ if (line.startsWith("new file mode")) {
11721
+ f.status = "added";
11722
+ continue;
11723
+ }
11724
+ if (line.startsWith("deleted file mode")) {
11725
+ f.status = "deleted";
11726
+ continue;
11727
+ }
11728
+ if (line.startsWith("rename from ") || line.startsWith("copy from ")) {
11729
+ f.oldPath = cleanPath(line.replace(/^(rename|copy) from /, ""));
11730
+ f.status = "renamed";
11731
+ continue;
11732
+ }
11733
+ if (line.startsWith("rename to ") || line.startsWith("copy to ")) {
11734
+ f.path = cleanPath(line.replace(/^(rename|copy) to /, ""));
11735
+ f.status = "renamed";
11736
+ continue;
11737
+ }
11738
+ if (line.startsWith("Binary files ")) {
11739
+ f.binary = true;
11740
+ continue;
11741
+ }
11742
+ if (line.startsWith("--- ")) {
11743
+ const p = stripAB(line.slice(4));
11744
+ if (p === "/dev/null") f.status = "added";
11745
+ continue;
11746
+ }
11747
+ if (line.startsWith("+++ ")) {
11748
+ const p = stripAB(line.slice(4));
11749
+ if (p === "/dev/null") f.status = "deleted";
11750
+ else if (p) f.path = p;
11751
+ continue;
11752
+ }
11753
+ if (line.startsWith("@@")) {
11754
+ const m = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@(.*)$/);
11755
+ if (m) {
11756
+ oldNo = Number(m[1]);
11757
+ newNo = Number(m[2]);
11758
+ hunk = { header: (m[3] ?? "").trim(), lines: [] };
11759
+ f.hunks.push(hunk);
11760
+ }
11761
+ continue;
11762
+ }
11763
+ if (!hunk) continue;
11764
+ if (line.startsWith("\\")) continue;
11765
+ const tag = line[0];
11766
+ const body = line.slice(1);
11767
+ if (tag === "+") {
11768
+ hunk.lines.push({ k: "add", o: null, n: newNo++, t: body });
11769
+ f.added++;
11770
+ } else if (tag === "-") {
11771
+ hunk.lines.push({ k: "del", o: oldNo++, n: null, t: body });
11772
+ f.removed++;
11773
+ } else if (tag === " ") {
11774
+ hunk.lines.push({ k: "ctx", o: oldNo++, n: newNo++, t: body });
11775
+ }
11776
+ }
11777
+ push();
11778
+ return files;
11779
+ }
11780
+ function truncateFile(f) {
11781
+ let count = 0;
11782
+ const hunks = [];
11783
+ let cut = false;
11784
+ for (const h of f.hunks) {
11785
+ if (count >= MAX_LINES_PER_FILE) {
11786
+ cut = true;
11787
+ break;
11788
+ }
11789
+ const room = MAX_LINES_PER_FILE - count;
11790
+ if (h.lines.length <= room) {
11791
+ hunks.push(h);
11792
+ count += h.lines.length;
11793
+ } else {
11794
+ hunks.push({ header: h.header, lines: h.lines.slice(0, room) });
11795
+ count += room;
11796
+ cut = true;
11797
+ break;
11798
+ }
11799
+ }
11800
+ return cut ? { ...f, hunks, truncated: true } : f;
11801
+ }
11802
+ async function resolveBase(wt, baseBranch) {
11803
+ const tryRef = async (ref) => {
11804
+ const sha = (await $4({ reject: false })`git -C ${wt} rev-parse --verify --quiet ${ref + "^{commit}"}`).stdout.trim();
11805
+ if (!sha) return null;
11806
+ const mb2 = (await $4({ reject: false })`git -C ${wt} merge-base HEAD ${ref}`).stdout.trim();
11807
+ return mb2 || null;
11808
+ };
11809
+ const originHead = (await $4({ reject: false })`git -C ${wt} symbolic-ref --quiet --short refs/remotes/origin/HEAD`).stdout.trim();
11810
+ const candidates = [
11811
+ ...baseBranch ? [`origin/${baseBranch}`, baseBranch] : [],
11812
+ originHead,
11813
+ "origin/main",
11814
+ "origin/master",
11815
+ "main",
11816
+ "master"
11817
+ ].filter(Boolean);
11818
+ for (const c of candidates) {
11819
+ const mb2 = await tryRef(c);
11820
+ if (mb2) return mb2;
11821
+ }
11822
+ return null;
11823
+ }
11824
+ function mtime(path) {
11825
+ try {
11826
+ return statSync10(path).mtimeMs;
11827
+ } catch {
11828
+ return 0;
11829
+ }
11830
+ }
11831
+ async function collectRunDiff(worktree, opts = {}) {
11832
+ const stub = (reason) => ({ ok: false, reason, pr: opts.pr ?? null, files: [], added: 0, removed: 0 });
11833
+ const gone = opts.status === "queued" ? "\u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u2014 \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442" : "worktree \u0443\u0431\u0440\u0430\u043D \u2014 \u043A\u043E\u0434 \u0441\u043C\u043E\u0442\u0440\u0438 \u0432 PR";
11834
+ if (!worktree) return stub(gone);
11835
+ const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
11836
+ if (isWt !== "true") return stub(gone);
11837
+ const base3 = await resolveBase(worktree, opts.baseBranch);
11838
+ const from = base3 ?? "HEAD";
11839
+ const trackedTxt = (await $4({ reject: false })`git -C ${worktree} diff --no-color --find-renames ${from} --`).stdout;
11840
+ let files = parseUnifiedDiff(trackedTxt);
11841
+ const untrackedRaw = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
11842
+ const untracked = untrackedRaw.split("\0").filter(Boolean).slice(0, MAX_UNTRACKED);
11843
+ for (const rel of untracked) {
11844
+ const t = (await $4({ reject: false })`git -C ${worktree} diff --no-color --no-index -- /dev/null ${rel}`).stdout;
11845
+ for (const pf of parseUnifiedDiff(t)) {
11846
+ pf.status = "added";
11847
+ if (!pf.path || pf.path === "/dev/null") pf.path = rel;
11848
+ files.push(pf);
11849
+ }
11850
+ }
11851
+ files.sort((a, b) => mtime(join25(worktree, b.path)) - mtime(join25(worktree, a.path)));
11852
+ const truncatedFiles = files.length > MAX_FILES;
11853
+ if (truncatedFiles) files = files.slice(0, MAX_FILES);
11854
+ const added = files.reduce((s, f) => s + f.added, 0);
11855
+ const removed = files.reduce((s, f) => s + f.removed, 0);
11856
+ const out = files.map(truncateFile);
11857
+ return {
11858
+ ok: true,
11859
+ base: base3 ? base3.slice(0, 9) : null,
11860
+ files: out,
11861
+ added,
11862
+ removed,
11863
+ ...truncatedFiles ? { truncated: true } : {}
11864
+ };
11865
+ }
11866
+ async function changedPaths(worktree, opts = {}) {
11867
+ if (!worktree) return [];
11868
+ try {
11869
+ const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
11870
+ if (isWt !== "true") return [];
11871
+ const from = await resolveBase(worktree, opts.baseBranch) ?? "HEAD";
11872
+ const tracked = (await $4({ reject: false })`git -C ${worktree} diff --name-only --find-renames ${from} --`).stdout;
11873
+ const untracked = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
11874
+ const all = [...tracked.split("\n"), ...untracked.split("\0")].map((s) => s.trim()).filter(Boolean);
11875
+ return [...new Set(all)].sort();
11876
+ } catch {
11877
+ return [];
11878
+ }
11879
+ }
11880
+
11881
+ // src/zoneNotes.ts
11882
+ init_src();
11883
+ function parseZoneMemory(raw, zone) {
11884
+ const key = normalizeZoneLabel(zone);
11885
+ if (!key || !raw || typeof raw !== "object" || Array.isArray(raw)) return [];
11886
+ const notes = raw[key];
11887
+ if (!Array.isArray(notes)) return [];
11888
+ const out = [];
11889
+ for (const item of notes) {
11890
+ if (!item || typeof item !== "object") continue;
11891
+ const n = item;
11892
+ const id = typeof n.id === "string" ? n.id : "";
11893
+ const claim = typeof n.claim === "string" ? n.claim.trim() : "";
11894
+ if (id && claim) out.push({ id, claim });
11895
+ }
11896
+ return out;
11897
+ }
11898
+ function normalizeZoneLabel(zone) {
11899
+ return (zone ?? "").trim().replace(/^area:/i, "").trim().toLowerCase();
11900
+ }
11901
+ function renderZoneMemoryBlock(zone, notes) {
11902
+ const label2 = normalizeZoneLabel(zone);
11903
+ if (!label2) return "";
11904
+ const head = notes.length ? [
11905
+ `\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u2014 \u0447\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u0432\u044B\u044F\u0441\u043D\u0438\u043B \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 (\u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u043E\u043C, \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u043A\u043E\u0434\u0430):`,
11906
+ ...notes.map((n) => `\u2022 [${n.id}] ${n.claim}`),
11907
+ "\u042D\u0442\u043E \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438, \u0430 \u043D\u0435 \u0438\u0441\u0442\u0438\u043D\u0430 \u0432 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0439 \u0438\u043D\u0441\u0442\u0430\u043D\u0446\u0438\u0438: \u0440\u0430\u0437\u043E\u0448\u043B\u043E\u0441\u044C \u0441 \u043A\u043E\u0434\u043E\u043C \u2014 \u0432\u0435\u0440\u044C \u043A\u043E\u0434\u0443 \u0438 \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u0441\u0434\u0430\u0447\u0435.",
11908
+ `\u041F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u0430\u0441\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u0451 \u0432 done: \`zone_notes_used: ["${notes[0].id}"]\` (id \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0432\u044B\u0448\u0435 \u0438\u043B\u0438 \u0441\u0430\u043C \u0442\u0435\u043A\u0441\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F). \u042D\u0442\u043E \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0435, \u0447\u0435\u043C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0436\u0438\u0432\u0451\u0442: \u043F\u0430\u043C\u044F\u0442\u044C \u0437\u043E\u043D\u044B \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430, \u0438 \u0432\u044B\u0442\u0435\u0441\u043D\u044F\u0435\u0442\u0441\u044F \u0442\u0430, \u0432 \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0440\u043E\u0439 \u0434\u043E\u043B\u044C\u0448\u0435 \u0432\u0441\u0435\u0445 \u043D\u0435 \u0443\u0431\u0435\u0436\u0434\u0430\u043B\u0441\u044F. \u041D\u0435 \u043F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u043E\u0441\u044C \u043D\u0438\u0447\u0435\u0433\u043E \u2014 \u043D\u0435 \u043D\u0430\u0437\u044B\u0432\u0430\u0439 \u043D\u0438\u0447\u0435\u0433\u043E, \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434.`
11909
+ ] : [`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u043F\u0443\u0441\u0442\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B.`];
11910
+ return [
11911
+ ...head,
11912
+ `\u{1F9E0} \u0417\u0410\u041C\u0415\u0422\u041A\u0410 \u0412 \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \u2014 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0434\u043E ${ZONE_NOTES_PER_RUN_MAX} \u0448\u0442\u0443\u043A: \`zone_notes: [{ zone: "${label2}", claim: "\u2026", why: "\u2026" }]\` (zone \u043C\u043E\u0436\u043D\u043E \u043D\u0435 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u2014 \u0432\u043E\u0437\u044C\u043C\u0451\u0442\u0441\u044F \u0442\u0432\u043E\u044F). \u0427\u0422\u041E \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 (\xAB\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0447\u0438\u0442\u0430\u044E\u0442 \u0438\u0437 \u043E\u0434\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u2026\xBB), \u0433\u0440\u0430\u0431\u043B\u0438 \u0441 \u0446\u0435\u043D\u043E\u0439 (\xAB\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043D\u0430\u043A\u0430\u0442\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u0442\u0435\u043C, \u0447\u0435\u043C \u043A\u0430\u0436\u0435\u0442\u0441\u044F, \u2014 \u0441\u0442\u043E\u0438\u043B\u043E \u0447\u0430\u0441\u0430\xBB), \u043E\u0442\u0431\u0440\u043E\u0448\u0435\u043D\u043D\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439. \u0427\u0422\u041E \u041D\u0415 \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043A\u043E\u0434\u0430 (\xAB\u0445\u0435\u043D\u0434\u043B\u0435\u0440 \u043B\u0435\u0436\u0438\u0442 \u0432 handlers.ts\xBB, \xAB\u044D\u0442\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F \u0434\u0435\u043B\u0430\u0435\u0442 \u0442\u043E-\u0442\u043E\xBB) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0435\u0442 \u044D\u0442\u043E \u0441\u0430\u043C \u0438 \u0431\u044B\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043C \u0442\u0432\u043E\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u043D\u0438\u0447\u0435\u0433\u043E \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u043F\u0438\u0441\u043E\u043A \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434 \u043F\u0440\u043E\u0433\u043E\u043D\u0430.`
11913
+ ].join("\n");
11914
+ }
11915
+ function collectZoneNoteConfirmations(report) {
11916
+ const seen = /* @__PURE__ */ new Set();
11917
+ const out = [];
11918
+ for (const raw of report.used ?? []) {
11919
+ const s = typeof raw === "string" ? raw.trim() : "";
11920
+ if (!s || seen.has(s)) continue;
11921
+ seen.add(s);
11922
+ out.push(s);
11923
+ }
11924
+ return out;
11925
+ }
11926
+
11927
+ // src/droneServer.ts
11928
+ init_src();
11929
+ init_src();
11930
+
11931
+ // src/reviewCycle.ts
11932
+ init_src();
11933
+ function needsFixIteration(run) {
11934
+ return run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict));
11935
+ }
11936
+ function reviewTransitionPatch(run, prUrl) {
11937
+ const isFixIteration = needsFixIteration(run);
11938
+ if (isFixIteration) {
11939
+ const cycle = (run.review_cycle ?? 0) + 1;
11940
+ return {
11941
+ isFixIteration: true,
11942
+ cycle,
11943
+ patch: { status: "review", pr_url: prUrl, review_cycle: cycle, review_verdict: null, verify_verdict: null }
11944
+ };
11945
+ }
11946
+ return { isFixIteration: false, cycle: null, patch: { status: "review", pr_url: prUrl } };
11947
+ }
11948
+ function branchFromPrPatch(currentBranch, headBranch) {
11949
+ if (!headBranch || headBranch === currentBranch) return null;
11950
+ return {
11951
+ patch: { branch: headBranch },
11952
+ event: currentBranch ? `\u0432\u0435\u0442\u043A\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0432\u0437\u044F\u0442\u0430 \u0443 PR: ${currentBranch} \u2192 ${headBranch} (\u043F\u043E\u043B\u0435 \u0431\u044B\u043B\u043E \u0441\u043E\u0431\u0440\u0430\u043D\u043E \u0438\u0437 \u0441\u043B\u0430\u0433\u0430 \u2014 TASWM-356)` : `\u0432\u0435\u0442\u043A\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0432\u0437\u044F\u0442\u0430 \u0443 PR: ${headBranch} (TASWM-356)`
11953
+ };
11954
+ }
11955
+ function prLiveness(st) {
11956
+ if (!st) return "unknown";
11957
+ if (st.merged) return "merged";
11958
+ const state = (st.state ?? "").toUpperCase();
11959
+ if (state === "MERGED") return "merged";
11960
+ if (state === "CLOSED") return "closed";
11961
+ if (state === "UNKNOWN") return "unknown";
11962
+ return "open";
11963
+ }
11964
+ function applyReviewTransition(db, run, prUrl) {
11965
+ const { patch, ...t } = reviewTransitionPatch(run, prUrl);
11966
+ db.updateRun(run.id, patch);
11967
+ return t;
11968
+ }
11969
+
11970
+ // src/droneServer.ts
11971
+ init_src();
11972
+ function readTaskFailureInstruction(slug, failure) {
11973
+ const why = describeToolFailure(failure);
11974
+ if (TOOL_FAILURE_RETRIABLE[failure.code]) {
11975
+ return `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 ${slug}: ${why}. \u042D\u0442\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u0438 \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E\u043A\u0430 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0436\u0435 \u0432 \u043F\u0440\u043E\u043C\u043F\u0442\u0435, \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 read_task \u0435\u0449\u0451 \u0440\u0430\u0437 \u043F\u043E\u0437\u0436\u0435 (\u043F\u0435\u0440\u0435\u0434 \u0441\u0434\u0430\u0447\u0435\u0439 \u2014 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E).`;
11976
+ }
11977
+ return [
11978
+ `\u26D4 \u0417\u0410\u0414\u0410\u0427\u0423 ${slug} \u041F\u0420\u041E\u0427\u0418\u0422\u0410\u0422\u042C \u041D\u0415\u041B\u042C\u0417\u042F: ${why}.`,
11979
+ "\u041F\u043E\u0432\u0442\u043E\u0440 \u0442\u0443\u0442 \u043D\u0435 \u043F\u043E\u043C\u043E\u0436\u0435\u0442 \u2014 \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043D\u0435 \u0432 \u0441\u0435\u0442\u0438, \u043E\u043D\u0430 \u0441\u0430\u043C\u0430 \u043D\u0435 \u0440\u0430\u0437\u043E\u0439\u0434\u0451\u0442\u0441\u044F.",
11980
+ "\u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E \u0441\u043B\u0430\u0433\u0443 \u0438 \u043F\u043E \u043F\u0430\u043C\u044F\u0442\u0438: \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0442\u044B \u043D\u0435 \u0432\u0438\u0434\u0435\u043B, \u043D\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0438\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 (TASWM-288).",
11981
+ "\u0421\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0447\u0435\u0440\u0435\u0437 ask() \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u043C\u0443 \u044D\u0442\u0443 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0434\u043E\u0441\u043B\u043E\u0432\u043D\u043E \u2014 \u0438 \u0434\u0430\u043B\u044C\u0448\u0435 \u0434\u0435\u043B\u0430\u0439 \u0442\u043E, \u0447\u0442\u043E \u043E\u043D \u043E\u0442\u0432\u0435\u0442\u0438\u0442."
11982
+ ].join("\n");
11983
+ }
11984
+ function freezeInstruction(branch, reason = "\u0435\u0433\u043E \u043F\u0443\u043B\u044C\u0441 (heartbeat) \u0443\u0441\u0442\u0430\u0440\u0435\u043B") {
11985
+ return [
11986
+ `\u26D4 OVERMIND \u041D\u0415\u0414\u041E\u0421\u0422\u0423\u041F\u0415\u041D \u2014 ${reason}. \u041D\u0430\u0434\u0437\u043E\u0440\u0430 \u043D\u0430\u0434 \u0442\u043E\u0431\u043E\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442: \u043D\u0435\u043A\u043E\u043C\u0443`,
11987
+ "\u043F\u0440\u0438\u043D\u044F\u0442\u044C \u0442\u0432\u043E\u0439 \u0432\u043E\u043F\u0440\u043E\u0441, \u0437\u0430\u043C\u0435\u0442\u0438\u0442\u044C \u0441\u043C\u0435\u0440\u0442\u044C, \u0441\u043C\u0435\u0440\u0436\u0438\u0442\u044C PR \u0438\u043B\u0438 \u0440\u0430\u0437\u0440\u0443\u043B\u0438\u0442\u044C \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442. \u0420\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u0432\u0441\u043B\u0435\u043F\u0443\u044E \u043D\u0435\u043B\u044C\u0437\u044F.",
11988
+ "\u0417\u0410\u041C\u0420\u0418 \u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0441\u044C, \u0447\u0442\u043E\u0431\u044B \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u043F\u0440\u043E\u043F\u0430\u043B\u0430:",
11989
+ "1. \u0417\u0430\u043A\u043E\u043C\u043C\u0438\u0442\u044C \u0412\u0421\u042E \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043A\u0430\u043A \u0435\u0441\u0442\u044C: `git add -A && git commit` (\u0442\u0440\u0435\u0439\u043B\u0435\u0440 \u043F\u0440\u043E \u0434\u0440\u043E\u043D\u0430 \u2014 \u043A\u0430\u043A \u043E\u0431\u044B\u0447\u043D\u043E).",
11990
+ " \u041D\u043E\u0432\u044B\u0439 \u043A\u043E\u0434 \u041D\u0415 \u043F\u0438\u0448\u0438, \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u0430\u043B\u044C\u0448\u0435 \u041D\u0415 \u0434\u0435\u043B\u0430\u0439 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u043E\u0445\u0440\u0430\u043D\u0438 \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435, \u0431\u0435\u0437 \u043C\u0443\u0441\u043E\u0440\u043D\u044B\u0445 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432.",
11991
+ `2. \u0417\u0430\u043F\u0443\u0448\u044C \u0432\u0435\u0442\u043A\u0443: \`git push -u origin ${branch}\` \u2014 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u0443\u043C\u0440\u0451\u0442 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u043C.`,
11992
+ "3. \u0417\u0430\u0432\u0435\u0440\u0448\u0438 \u0440\u0430\u0431\u043E\u0442\u0443 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u041D\u0415 \u0437\u043E\u0432\u0438 (\u043D\u0438 report_step, \u043D\u0438 ask, \u043D\u0438 done). \u041F\u0440\u043E\u0441\u0442\u043E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438 \u0441\u0435\u0441\u0441\u0438\u044E.",
11993
+ "\u041F\u0440\u043E\u0433\u043E\u043D \u043F\u043E\u043C\u0435\u0447\u0435\u043D \xABpaused\xBB. \u041A\u043E\u0433\u0434\u0430 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u043D\u043E\u0432\u044B\u0439 overmind, \u043E\u043D \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442 \u0442\u0435\u0431\u044F \u0441 \u044D\u0442\u043E\u0433\u043E \u043C\u0435\u0441\u0442\u0430 (--continue) \u2014",
11587
11994
  "\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0448\u044C \u043E\u0442\u0442\u0443\u0434\u0430. \u041D\u0435 \u043F\u0435\u0440\u0435\u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439 \u0438 \u043D\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0443 \u0441\u0435\u0439\u0447\u0430\u0441."
11588
11995
  ].join("\n");
11589
11996
  }
@@ -11769,19 +12176,36 @@ ${failText}` : failText }],
11769
12176
  );
11770
12177
  server.tool(
11771
12178
  "return_task",
11772
- "\u0412\u0435\u0440\u043D\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0410\u0412\u0422\u041E\u0420\u0423 \u043D\u0430 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u0435 \u0438 \u0437\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D (\u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F, \u0440\u043E\u0439 \u0432\u043E\u0437\u044C\u043C\u0451\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443). \u0417\u043E\u0432\u0438, \u0422\u041E\u041B\u042C\u041A\u041E \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043D\u0438 \u0432 \u043E\u0434\u043D\u043E\u043C \u0440\u0430\u0437\u0443\u043C\u043D\u043E\u043C \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0438 \u0418\u041B\u0418 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0414\u0432\u0430 \u043E\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B\u0445 \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u044F \u2014 \u041D\u0415 \u043F\u043E\u0432\u043E\u0434: \u0432\u044B\u0431\u0435\u0440\u0438 \u043E\u0447\u0435\u0432\u0438\u0434\u043D\u043E\u0435, \u0441\u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0430\u0437\u043E\u0432\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0443 \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438 PR. \u041F\u043E\u043D\u044F\u0442\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u043D\u043A\u0442\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u2014 \u0442\u043E\u0436\u0435 \u043D\u0435 \u043F\u043E\u0432\u043E\u0434: \u044D\u0442\u043E ask. \u0417\u0430\u0434\u0430\u0447\u0430 \u0443\u0439\u0434\u0451\u0442 \u0430\u0432\u0442\u043E\u0440\u0443 (\u043D\u0430\u0437\u043D\u0430\u0447\u0438\u043C \u043D\u0430 \u043D\u0435\u0433\u043E \u0438 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0451\u043C \u0432 \u0440\u0430\u0431\u043E\u0442\u0443), \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0443\u0435\u0434\u0443\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u043E\u0442 \u0442\u0432\u043E\u0435\u0433\u043E \u0438\u043C\u0435\u043D\u0438. \u041F\u043E\u0441\u043B\u0435 \u0432\u044B\u0437\u043E\u0432\u0430 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0437\u043E\u0432\u0438 \u2014 \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E.",
12179
+ "\u0412\u0435\u0440\u043D\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0410\u0412\u0422\u041E\u0420\u0423 \u043D\u0430 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u0435 \u0438 \u0437\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D (\u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F, \u0440\u043E\u0439 \u0432\u043E\u0437\u044C\u043C\u0451\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443). \u0417\u043E\u0432\u0438, \u0422\u041E\u041B\u042C\u041A\u041E \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043D\u0438 \u0432 \u043E\u0434\u043D\u043E\u043C \u0440\u0430\u0437\u0443\u043C\u043D\u043E\u043C \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0438 \u0418\u041B\u0418 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0414\u0432\u0430 \u043E\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B\u0445 \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u044F \u2014 \u041D\u0415 \u043F\u043E\u0432\u043E\u0434: \u0432\u044B\u0431\u0435\u0440\u0438 \u043E\u0447\u0435\u0432\u0438\u0434\u043D\u043E\u0435, \u0441\u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0430\u0437\u043E\u0432\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0443 \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438 PR. \u041F\u043E\u043D\u044F\u0442\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u043D\u043A\u0442\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u2014 \u0442\u043E\u0436\u0435 \u043D\u0435 \u043F\u043E\u0432\u043E\u0434: \u044D\u0442\u043E ask. \u041E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0439 \u0437\u0430\u043A\u043E\u043D\u043D\u044B\u0439 \u0441\u043B\u0443\u0447\u0430\u0439 \u2014 \u0437\u0430\u0434\u0430\u0447\u0430 \u0421\u041B\u0418\u0428\u041A\u041E\u041C \u0412\u0415\u041B\u0418\u041A\u0410 (reason=too_big): \u043F\u043E\u043D\u044F\u0442\u043D\u0430 \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u0430, \u043D\u043E \u044D\u0442\u043E \u043B\u0438\u0441\u0442 \u043D\u0430 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432, \u0438 \u0443\u043F\u0435\u0440\u0435\u0442\u044C\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 \u043D\u0430 \u0441\u0435\u0440\u0435\u0434\u0438\u043D\u0435 \u0445\u0443\u0436\u0435, \u0447\u0435\u043C \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0435\u0451 \u0441\u0440\u0430\u0437\u0443; \u0440\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435 \u043C\u043E\u0436\u0435\u0448\u044C \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0418\u0422\u042C (split), \u0430 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438 \u043D\u0435 \u0437\u0430\u0432\u043E\u0434\u0438 \u2014 \u044D\u0442\u043E \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0430\u0432\u0442\u043E\u0440\u0430. \u0417\u0430\u0434\u0430\u0447\u0430 \u0443\u0439\u0434\u0451\u0442 \u0430\u0432\u0442\u043E\u0440\u0443 (\u043D\u0430\u0437\u043D\u0430\u0447\u0438\u043C \u043D\u0430 \u043D\u0435\u0433\u043E \u0438 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0451\u043C \u0432 \u0440\u0430\u0431\u043E\u0442\u0443), \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0443\u0435\u0434\u0443\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u043E\u0442 \u0442\u0432\u043E\u0435\u0433\u043E \u0438\u043C\u0435\u043D\u0438. \u041F\u043E\u0441\u043B\u0435 \u0432\u044B\u0437\u043E\u0432\u0430 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0437\u043E\u0432\u0438 \u2014 \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E.",
11773
12180
  {
12181
+ // TASWM-505: КОД причины рядом с прозой. Проза объясняет автору ЭТУ задачу, код
12182
+ // складывается по бэклогу в распределение «чего в постановках не хватает чаще
12183
+ // всего» (аналитика роя). Обязательный: необязательное поле дрон пропустит, а
12184
+ // распределение по половине возвратов — не распределение.
12185
+ reason: z.enum(RETURN_REASONS).describe(
12186
+ "\u043A\u043E\u0434 \u043F\u0440\u0438\u0447\u0438\u043D\u044B: unclear_goal \u2014 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0427\u0422\u041E \u0434\u0435\u043B\u0430\u0442\u044C; no_acceptance \u2014 \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442; too_big \u2014 \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u043E, \u043D\u043E \u044D\u0442\u043E \u043D\u0435 \u0437\u0430\u0434\u0430\u0447\u0430, \u0430 \u043F\u0430\u0447\u043A\u0430 \u0437\u0430\u0434\u0430\u0447 (\u043B\u0438\u0441\u0442, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0437\u0430 \u043E\u0434\u0438\u043D \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u0434\u043E\u0432\u0435\u0441\u0442\u0438); contradiction \u2014 \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u0441\u0430\u043C\u0430 \u0441\u0435\u0431\u0435 \u0438\u043B\u0438 \u0443\u0436\u0435 \u043F\u0440\u0438\u043D\u044F\u0442\u043E\u043C\u0443 \u0440\u0435\u0448\u0435\u043D\u0438\u044E"
12187
+ ),
11774
12188
  missing: z.string().min(30).describe("\u0447\u0435\u0433\u043E \u0432 \u0437\u0430\u0434\u0430\u0447\u0435 \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442, \u0447\u0442\u043E\u0431\u044B \u0435\u0451 \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u0438\u043B\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u2014 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E, \u0430 \u043D\u0435 \xAB\u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E\xBB"),
11775
12189
  readings: z.array(z.string().min(10)).optional().describe("\u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u044F \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0442\u044B \u0440\u0430\u0441\u0441\u043C\u043E\u0442\u0440\u0435\u043B (\u0438 \u0447\u0435\u043C \u043A\u0430\u0436\u0434\u043E\u0435 \u043F\u043B\u043E\u0445\u043E/\u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0432\u043E)"),
11776
12190
  assumed: z.string().min(30).describe("\u0447\u0442\u043E \u0442\u044B \u043F\u0440\u0435\u0434\u043F\u043E\u043B\u043E\u0436\u0438\u043B \u0431\u044B \u0441\u0434\u0435\u043B\u0430\u0442\u044C, \u0435\u0441\u043B\u0438 \u0431\u044B \u043F\u0440\u0438\u0448\u043B\u043E\u0441\u044C \u0433\u0430\u0434\u0430\u0442\u044C, \u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u043D\u0435 \u0441\u0442\u0430\u043B"),
11777
- questions: z.array(z.string().min(10)).min(1).max(7).describe("\u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0430\u0432\u0442\u043E\u0440\u0443 \u2014 \u043E\u0442\u0432\u0435\u0442\u044B \u043D\u0430 \u043D\u0438\u0445 \u0434\u043E\u043B\u0436\u043D\u044B \u0434\u0435\u043B\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043C\u043E\u0439")
12191
+ questions: z.array(z.string().min(10)).min(1).max(7).describe("\u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0430\u0432\u0442\u043E\u0440\u0443 \u2014 \u043E\u0442\u0432\u0435\u0442\u044B \u043D\u0430 \u043D\u0438\u0445 \u0434\u043E\u043B\u0436\u043D\u044B \u0434\u0435\u043B\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043C\u043E\u0439"),
12192
+ // TASWM-505: разбиение — ПРЕДЛОЖЕНИЕ автору, а не действие дрона. Подзадачи дрон не
12193
+ // заводит сознательно: разбиение — продуктовое решение, а плохое разбиение хуже
12194
+ // никакого, потому что выглядит авторитетно (заведённые подзадачи прочтут как
12195
+ // согласованный план). Поэтому — строчками в комменте, которые автор правит или
12196
+ // выбрасывает одним движением.
12197
+ split: z.array(z.string().min(10)).max(7).optional().describe(
12198
+ "\u0422\u041E\u041B\u042C\u041A\u041E \u043A reason=too_big: \u043A\u0430\u043A \u0431\u044B \u0442\u044B \u0440\u0430\u0437\u0431\u0438\u043B \u0437\u0430\u0434\u0430\u0447\u0443 \u2014 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u043A\u0443\u0441\u043A\u0443 \u043D\u0430 \u0441\u0442\u0440\u043E\u043A\u0443. \u0423\u0435\u0434\u0435\u0442 \u0430\u0432\u0442\u043E\u0440\u0443 \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0415\u041D\u0418\u0415\u041C \u0432 \u0442\u043E\u043C \u0436\u0435 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0435; \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438 \u0441\u0430\u043C \u041D\u0415 \u0437\u0430\u0432\u043E\u0434\u0438 (\u0440\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435 \u2014 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0430\u0432\u0442\u043E\u0440\u0430)"
12199
+ )
11778
12200
  },
11779
12201
  guard(
11780
12202
  async ({
12203
+ reason,
11781
12204
  missing,
11782
12205
  readings,
11783
12206
  assumed,
11784
- questions
12207
+ questions,
12208
+ split
11785
12209
  }) => {
11786
12210
  if (await overmindDead()) return freeze();
11787
12211
  const run = await store.myRun();
@@ -11816,10 +12240,20 @@ ${failText}` : failText }],
11816
12240
  "",
11817
12241
  `${author ? `${author.name}, ` : ""}\u044F \u043D\u0435 \u0431\u0435\u0440\u0443\u0441\u044C \u0437\u0430 \u044D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443: \u0435\u0451 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C, \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u0430\u0432 \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0443 \u0437\u0430 \u0442\u0435\u0431\u044F. \u0427\u0442\u043E \u043C\u0435\u0448\u0430\u0435\u0442:`,
11818
12242
  "",
12243
+ `**\u041F\u0440\u0438\u0447\u0438\u043D\u0430.** ${returnReasonLabel(reason)} (\`${reason}\`).`,
12244
+ "",
11819
12245
  `**\u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442.** ${missing.trim()}`,
11820
12246
  ...readings?.length ? ["", "**\u041A\u0430\u043A \u044D\u0442\u043E \u043C\u043E\u0436\u043D\u043E \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C.**", ...readings.map((r) => `- ${r.trim()}`)] : [],
11821
12247
  "",
11822
12248
  `**\u0427\u0442\u043E \u044F \u043F\u0440\u0435\u0434\u043F\u043E\u043B\u043E\u0436\u0438\u043B \u0431\u044B \u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u043D\u0435 \u0441\u0442\u0430\u043B.** ${assumed.trim()}`,
12249
+ // Разбиение — только предложение и только к too_big (TASWM-505). Формулировка
12250
+ // «на твоё усмотрение» здесь не вежливость: заведённые подзадачи выглядели бы
12251
+ // согласованным планом, а это решение автора, и плохое разбиение хуже никакого.
12252
+ ...reason === "too_big" && split?.length ? [
12253
+ "",
12254
+ "**\u041A\u0430\u043A \u044D\u0442\u043E \u043C\u043E\u0436\u043D\u043E \u0440\u0430\u0437\u0431\u0438\u0442\u044C** \u2014 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435, \u043D\u0435 \u0440\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435: \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438 \u044F \u043D\u0435 \u0437\u0430\u0432\u043E\u0434\u0438\u043B, \u0440\u0435\u0448\u0430\u0442\u044C \u0442\u0435\u0431\u0435.",
12255
+ ...split.map((s) => `- ${s.trim()}`)
12256
+ ] : [],
11823
12257
  "",
11824
12258
  "**\u0412\u043E\u043F\u0440\u043E\u0441\u044B, \u043F\u043E\u0441\u043B\u0435 \u043E\u0442\u0432\u0435\u0442\u043E\u0432 \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0443 \u043C\u043E\u0436\u043D\u043E \u0431\u0440\u0430\u0442\u044C:**",
11825
12259
  ...questions.map((q, i) => `${i + 1}. ${q.trim()}`)
@@ -11863,9 +12297,9 @@ ${failText}` : failText }],
11863
12297
  };
11864
12298
  }
11865
12299
  }
11866
- const reason = `\u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u0430 \u0430\u0432\u0442\u043E\u0440\u0443${author ? ` (${author.name})` : ""}: ${missing.trim()}`;
11867
- await store.updateRun(null, { status: "returned", stop_reason: reason });
11868
- await store.addEvent(null, "returned", failures.length ? `${reason} \xB7 \u043D\u0435 \u0434\u043E\u0435\u0445\u0430\u043B\u043E: ${failures.join("; ")}` : reason);
12300
+ const stopReason = `\u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u0430 \u0430\u0432\u0442\u043E\u0440\u0443${author ? ` (${author.name})` : ""} \xB7 ${returnReasonLabel(reason)}: ${missing.trim()}`;
12301
+ await store.updateRun(null, { status: "returned", stop_reason: stopReason, return_reason: reason });
12302
+ await store.addEvent(null, "returned", failures.length ? `${stopReason} \xB7 \u043D\u0435 \u0434\u043E\u0435\u0445\u0430\u043B\u043E: ${failures.join("; ")}` : stopReason);
11869
12303
  return {
11870
12304
  content: [
11871
12305
  {
@@ -12211,9 +12645,11 @@ ${report}${shots.warning}`,
12211
12645
  const headBranch = opts.prHeadBranch ? await opts.prHeadBranch(pr_url).catch(() => null) : null;
12212
12646
  const branchFix = branchFromPrPatch(run?.branch, headBranch);
12213
12647
  if (branchFix) await store.addEvent(null, "step", branchFix.event);
12648
+ const held = await changedPaths(opts.worktree ?? process.cwd(), { baseBranch: run?.base_branch ?? null }).catch(() => []);
12649
+ const heldPatch = held.length ? { changed_paths: held } : {};
12214
12650
  if (run) {
12215
12651
  const t = reviewTransitionPatch(run, pr_url);
12216
- await store.updateRun(null, { ...t.patch, ...branchFix?.patch ?? {} });
12652
+ await store.updateRun(null, { ...t.patch, ...branchFix?.patch ?? {}, ...heldPatch });
12217
12653
  await store.addEvent(null, "pr_opened", pr_url);
12218
12654
  await postReport(reportSlug(run.task_slug), t.cycle ?? run.review_cycle ?? 0);
12219
12655
  const rulesNote = await offerRules(reportSlug(run.task_slug));
@@ -12228,7 +12664,7 @@ ${report}${shots.warning}`,
12228
12664
  }
12229
12665
  return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}${rulesNote}${zoneNote}` }] };
12230
12666
  }
12231
- await store.updateRun(null, { status: "review", pr_url, ...branchFix?.patch ?? {} });
12667
+ await store.updateRun(null, { status: "review", pr_url, ...branchFix?.patch ?? {}, ...heldPatch });
12232
12668
  await store.addEvent(null, "pr_opened", pr_url);
12233
12669
  const fallbackSlug = reportSlug(void 0);
12234
12670
  if (fallbackSlug) await postReport(fallbackSlug, void 0);
@@ -13532,20 +13968,20 @@ function makeMergeGateParts(deps) {
13532
13968
 
13533
13969
  // src/envGate.ts
13534
13970
  import { existsSync as existsSync15 } from "fs";
13535
- import { join as join26 } from "path";
13971
+ import { join as join27 } from "path";
13536
13972
  import { createConnection } from "net";
13537
13973
  import { execa as execa2 } from "execa";
13538
13974
 
13539
13975
  // src/platform.ts
13540
13976
  import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
13541
- import { delimiter, join as join25 } from "path";
13977
+ import { delimiter, join as join26 } from "path";
13542
13978
  function resolveCommandPath(name, env = process.env, platform = process.platform) {
13543
13979
  if (name.includes("/") || name.includes("\\")) return existsSync14(name) ? name : null;
13544
13980
  const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
13545
13981
  const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
13546
13982
  for (const dir of dirs) {
13547
13983
  for (const ext of exts) {
13548
- const candidate = join25(dir, name + ext);
13984
+ const candidate = join26(dir, name + ext);
13549
13985
  if (existsSync14(candidate)) return candidate;
13550
13986
  }
13551
13987
  }
@@ -13630,7 +14066,7 @@ var COMPOSE_MARKERS = [
13630
14066
  ];
13631
14067
  function detectRequirements(repo, io = {}) {
13632
14068
  const exists = io.exists ?? existsSync15;
13633
- const marker = COMPOSE_MARKERS.find((f) => exists(join26(repo, f)));
14069
+ const marker = COMPOSE_MARKERS.find((f) => exists(join27(repo, f)));
13634
14070
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
13635
14071
  }
13636
14072
  function resolveRequirements(repo, declared, io = {}) {
@@ -13765,210 +14201,10 @@ import { randomBytes as randomBytes3 } from "crypto";
13765
14201
  import { basename as basename5 } from "path";
13766
14202
  import { serve as serve2 } from "@hono/node-server";
13767
14203
 
13768
- // src/diff.ts
13769
- import { statSync as statSync10 } from "fs";
13770
- import { join as join27 } from "path";
13771
- import { $ as $4 } from "execa";
13772
- var MAX_FILES = 200;
13773
- var MAX_LINES_PER_FILE = 1500;
13774
- var MAX_UNTRACKED = 60;
13775
- function cleanPath(raw) {
13776
- let p = raw.trim();
13777
- if (p.startsWith('"') && p.endsWith('"') && p.length >= 2) p = p.slice(1, -1);
13778
- return p;
13779
- }
13780
- function stripAB(raw) {
13781
- const p = cleanPath(raw);
13782
- if (p === "/dev/null") return p;
13783
- if (p.startsWith("a/") || p.startsWith("b/")) return p.slice(2);
13784
- return p;
13785
- }
13786
- function parseUnifiedDiff(text) {
13787
- const files = [];
13788
- if (!text) return files;
13789
- const lines = text.split("\n");
13790
- let f = null;
13791
- let hunk = null;
13792
- let oldNo = 0;
13793
- let newNo = 0;
13794
- const push = () => {
13795
- if (f) files.push(f);
13796
- };
13797
- for (const line of lines) {
13798
- if (line.startsWith("diff --git ")) {
13799
- push();
13800
- hunk = null;
13801
- const m = line.match(/^diff --git (.+) (.+)$/);
13802
- const guess = m ? stripAB(m[2]) : "";
13803
- f = { path: guess, oldPath: null, status: "modified", added: 0, removed: 0, binary: false, hunks: [] };
13804
- continue;
13805
- }
13806
- if (!f) continue;
13807
- if (line.startsWith("new file mode")) {
13808
- f.status = "added";
13809
- continue;
13810
- }
13811
- if (line.startsWith("deleted file mode")) {
13812
- f.status = "deleted";
13813
- continue;
13814
- }
13815
- if (line.startsWith("rename from ") || line.startsWith("copy from ")) {
13816
- f.oldPath = cleanPath(line.replace(/^(rename|copy) from /, ""));
13817
- f.status = "renamed";
13818
- continue;
13819
- }
13820
- if (line.startsWith("rename to ") || line.startsWith("copy to ")) {
13821
- f.path = cleanPath(line.replace(/^(rename|copy) to /, ""));
13822
- f.status = "renamed";
13823
- continue;
13824
- }
13825
- if (line.startsWith("Binary files ")) {
13826
- f.binary = true;
13827
- continue;
13828
- }
13829
- if (line.startsWith("--- ")) {
13830
- const p = stripAB(line.slice(4));
13831
- if (p === "/dev/null") f.status = "added";
13832
- continue;
13833
- }
13834
- if (line.startsWith("+++ ")) {
13835
- const p = stripAB(line.slice(4));
13836
- if (p === "/dev/null") f.status = "deleted";
13837
- else if (p) f.path = p;
13838
- continue;
13839
- }
13840
- if (line.startsWith("@@")) {
13841
- const m = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@(.*)$/);
13842
- if (m) {
13843
- oldNo = Number(m[1]);
13844
- newNo = Number(m[2]);
13845
- hunk = { header: (m[3] ?? "").trim(), lines: [] };
13846
- f.hunks.push(hunk);
13847
- }
13848
- continue;
13849
- }
13850
- if (!hunk) continue;
13851
- if (line.startsWith("\\")) continue;
13852
- const tag = line[0];
13853
- const body = line.slice(1);
13854
- if (tag === "+") {
13855
- hunk.lines.push({ k: "add", o: null, n: newNo++, t: body });
13856
- f.added++;
13857
- } else if (tag === "-") {
13858
- hunk.lines.push({ k: "del", o: oldNo++, n: null, t: body });
13859
- f.removed++;
13860
- } else if (tag === " ") {
13861
- hunk.lines.push({ k: "ctx", o: oldNo++, n: newNo++, t: body });
13862
- }
13863
- }
13864
- push();
13865
- return files;
13866
- }
13867
- function truncateFile(f) {
13868
- let count = 0;
13869
- const hunks = [];
13870
- let cut = false;
13871
- for (const h of f.hunks) {
13872
- if (count >= MAX_LINES_PER_FILE) {
13873
- cut = true;
13874
- break;
13875
- }
13876
- const room = MAX_LINES_PER_FILE - count;
13877
- if (h.lines.length <= room) {
13878
- hunks.push(h);
13879
- count += h.lines.length;
13880
- } else {
13881
- hunks.push({ header: h.header, lines: h.lines.slice(0, room) });
13882
- count += room;
13883
- cut = true;
13884
- break;
13885
- }
13886
- }
13887
- return cut ? { ...f, hunks, truncated: true } : f;
13888
- }
13889
- async function resolveBase(wt, baseBranch) {
13890
- const tryRef = async (ref) => {
13891
- const sha = (await $4({ reject: false })`git -C ${wt} rev-parse --verify --quiet ${ref + "^{commit}"}`).stdout.trim();
13892
- if (!sha) return null;
13893
- const mb2 = (await $4({ reject: false })`git -C ${wt} merge-base HEAD ${ref}`).stdout.trim();
13894
- return mb2 || null;
13895
- };
13896
- const originHead = (await $4({ reject: false })`git -C ${wt} symbolic-ref --quiet --short refs/remotes/origin/HEAD`).stdout.trim();
13897
- const candidates = [
13898
- ...baseBranch ? [`origin/${baseBranch}`, baseBranch] : [],
13899
- originHead,
13900
- "origin/main",
13901
- "origin/master",
13902
- "main",
13903
- "master"
13904
- ].filter(Boolean);
13905
- for (const c of candidates) {
13906
- const mb2 = await tryRef(c);
13907
- if (mb2) return mb2;
13908
- }
13909
- return null;
13910
- }
13911
- function mtime(path) {
13912
- try {
13913
- return statSync10(path).mtimeMs;
13914
- } catch {
13915
- return 0;
13916
- }
13917
- }
13918
- async function collectRunDiff(worktree, opts = {}) {
13919
- const stub = (reason) => ({ ok: false, reason, pr: opts.pr ?? null, files: [], added: 0, removed: 0 });
13920
- const gone = opts.status === "queued" ? "\u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u2014 \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442" : "worktree \u0443\u0431\u0440\u0430\u043D \u2014 \u043A\u043E\u0434 \u0441\u043C\u043E\u0442\u0440\u0438 \u0432 PR";
13921
- if (!worktree) return stub(gone);
13922
- const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
13923
- if (isWt !== "true") return stub(gone);
13924
- const base3 = await resolveBase(worktree, opts.baseBranch);
13925
- const from = base3 ?? "HEAD";
13926
- const trackedTxt = (await $4({ reject: false })`git -C ${worktree} diff --no-color --find-renames ${from} --`).stdout;
13927
- let files = parseUnifiedDiff(trackedTxt);
13928
- const untrackedRaw = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
13929
- const untracked = untrackedRaw.split("\0").filter(Boolean).slice(0, MAX_UNTRACKED);
13930
- for (const rel of untracked) {
13931
- const t = (await $4({ reject: false })`git -C ${worktree} diff --no-color --no-index -- /dev/null ${rel}`).stdout;
13932
- for (const pf of parseUnifiedDiff(t)) {
13933
- pf.status = "added";
13934
- if (!pf.path || pf.path === "/dev/null") pf.path = rel;
13935
- files.push(pf);
13936
- }
13937
- }
13938
- files.sort((a, b) => mtime(join27(worktree, b.path)) - mtime(join27(worktree, a.path)));
13939
- const truncatedFiles = files.length > MAX_FILES;
13940
- if (truncatedFiles) files = files.slice(0, MAX_FILES);
13941
- const added = files.reduce((s, f) => s + f.added, 0);
13942
- const removed = files.reduce((s, f) => s + f.removed, 0);
13943
- const out = files.map(truncateFile);
13944
- return {
13945
- ok: true,
13946
- base: base3 ? base3.slice(0, 9) : null,
13947
- files: out,
13948
- added,
13949
- removed,
13950
- ...truncatedFiles ? { truncated: true } : {}
13951
- };
13952
- }
13953
- async function changedPaths(worktree, opts = {}) {
13954
- if (!worktree) return [];
13955
- try {
13956
- const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
13957
- if (isWt !== "true") return [];
13958
- const from = await resolveBase(worktree, opts.baseBranch) ?? "HEAD";
13959
- const tracked = (await $4({ reject: false })`git -C ${worktree} diff --name-only --find-renames ${from} --`).stdout;
13960
- const untracked = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
13961
- const all = [...tracked.split("\n"), ...untracked.split("\0")].map((s) => s.trim()).filter(Boolean);
13962
- return [...new Set(all)].sort();
13963
- } catch {
13964
- return [];
13965
- }
13966
- }
13967
-
13968
14204
  // package.json
13969
14205
  var package_default = {
13970
14206
  name: "@taskless-app/swarm",
13971
- version: "0.6.25",
14207
+ version: "0.6.27",
13972
14208
  type: "module",
13973
14209
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
13974
14210
  keywords: [
@@ -14019,7 +14255,7 @@ var package_default = {
14019
14255
  execa: "^10.0.0",
14020
14256
  zod: "3.25.76"
14021
14257
  },
14022
- gitHead: "36aaed0414a46ea272ad762b81e051fecec40eaa"
14258
+ gitHead: "2e1ed83014422ae577be60271adc79e291b9e2ab"
14023
14259
  };
14024
14260
 
14025
14261
  // src/version.ts
@@ -16401,6 +16637,12 @@ function predictForRun(model, run) {
16401
16637
  function short(files, n = 3) {
16402
16638
  return files.slice(0, n).join(", ") + (files.length > n ? ` \u0438 \u0435\u0449\u0451 ${files.length - n}` : "");
16403
16639
  }
16640
+ function heldBecause(w) {
16641
+ const parts = [];
16642
+ if (w.prUrl) parts.push(`\u0440\u0430\u0431\u043E\u0442\u0430 \u0441\u0434\u0430\u043D\u0430, PR \u0435\u0449\u0451 \u043D\u0435 \u0432\u043B\u0438\u0442: ${w.prUrl}`);
16643
+ if (!w.actual) parts.push(w.prUrl ? "\u0444\u0430\u0439\u043B\u044B \u0432\u0437\u044F\u0442\u044B \u043F\u043E \u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0430\u043D\u0438\u044E \u2014 \u0441\u043D\u0438\u043C\u043A\u0430 \u043D\u0435 \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C" : "\u043F\u043E \u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0430\u043D\u0438\u044E \u2014 \u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u043D\u0435 \u043D\u0430\u0447\u0430\u043B \u043F\u0438\u0441\u0430\u0442\u044C \u043A\u043E\u0434");
16644
+ return parts.length ? ` (${parts.join("; ")})` : "";
16645
+ }
16404
16646
  var SPINE_PREDICTION_WEIGHT = 0.7;
16405
16647
  function spineOfPrediction(spine, predicted) {
16406
16648
  return predicted.filter((p) => spine.has(p.path) && p.weight >= SPINE_PREDICTION_WEIGHT).map((p) => p.path);
@@ -16421,7 +16663,7 @@ function decideConflict({ model, candidate, active }) {
16421
16663
  return {
16422
16664
  kind: "wait",
16423
16665
  blockedBy: w.run,
16424
- message: `\u044D\u0442\u0438 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0440\u043E\u0433\u0430\u044E\u0442 \u043E\u0434\u043D\u0438 \u0444\u0430\u0439\u043B\u044B \u043E\u0431\u0449\u0435\u0433\u043E \u0445\u0440\u0435\u0431\u0442\u0430 (${short(shared)}) \u2014 \u0442\u0430\u043A\u0438\u0435 \u0438\u0434\u0443\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u0437\u043E\u043D\u044B: \u0436\u0434\u0443 ${w.run.task_slug} (#${w.run.id})` + (w.actual ? "" : " (\u043F\u043E \u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0430\u043D\u0438\u044E \u2014 \u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u043D\u0435 \u043D\u0430\u0447\u0430\u043B \u043F\u0438\u0441\u0430\u0442\u044C \u043A\u043E\u0434)")
16666
+ message: `\u044D\u0442\u0438 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0440\u043E\u0433\u0430\u044E\u0442 \u043E\u0434\u043D\u0438 \u0444\u0430\u0439\u043B\u044B \u043E\u0431\u0449\u0435\u0433\u043E \u0445\u0440\u0435\u0431\u0442\u0430 (${short(shared)}) \u2014 \u0442\u0430\u043A\u0438\u0435 \u0438\u0434\u0443\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u0437\u043E\u043D\u044B: \u0436\u0434\u0443 ${w.run.task_slug} (#${w.run.id})` + heldBecause(w)
16425
16667
  };
16426
16668
  }
16427
16669
  }
@@ -16432,7 +16674,7 @@ function decideConflict({ model, candidate, active }) {
16432
16674
  return {
16433
16675
  kind: "wait",
16434
16676
  blockedBy: w.run,
16435
- message: `\u044D\u0442\u0438 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0440\u043E\u0433\u0430\u044E\u0442 ${what} \u2014 \u0441\u0442\u0430\u0432\u0438\u043C \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E: \u0436\u0434\u0443 ${w.run.task_slug} (#${w.run.id})` + (w.actual ? "" : " (\u043F\u043E \u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0430\u043D\u0438\u044E \u2014 \u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u043D\u0435 \u043D\u0430\u0447\u0430\u043B \u043F\u0438\u0441\u0430\u0442\u044C \u043A\u043E\u0434)")
16677
+ message: `\u044D\u0442\u0438 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0440\u043E\u0433\u0430\u044E\u0442 ${what} \u2014 \u0441\u0442\u0430\u0432\u0438\u043C \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E: \u0436\u0434\u0443 ${w.run.task_slug} (#${w.run.id})` + heldBecause(w)
16436
16678
  };
16437
16679
  }
16438
16680
  return { kind: "clear" };
@@ -16485,10 +16727,16 @@ var ActiveFilesCache = class {
16485
16727
  this.cache.set(run.id, { at: now, files });
16486
16728
  return files;
16487
16729
  }
16488
- /** Забыть всех, кого нет среди живых (дрон закончил — его файлы больше не заняты). */
16489
- prune(liveIds) {
16490
- const live = new Set(liveIds);
16491
- for (const id of [...this.cache.keys()]) if (!live.has(id)) this.cache.delete(id);
16730
+ /**
16731
+ * Забыть всех, кто больше НЕ ДЕРЖИТ файлы. Раньше здесь стоял список ЖИВЫХ дронов и
16732
+ * комментарий «дрон закончил — его файлы больше не заняты»; это и была дырка TASWM-506:
16733
+ * дрон заканчивал, открывал PR, PR висел непомерженным — а строки уже считались
16734
+ * свободными. Держатель теперь переживает своего дрона и уходит из кэша на мерже
16735
+ * (прогон выходит из `review`), см. holdsFiles.
16736
+ */
16737
+ prune(holderIds) {
16738
+ const holders = new Set(holderIds);
16739
+ for (const id of [...this.cache.keys()]) if (!holders.has(id)) this.cache.delete(id);
16492
16740
  }
16493
16741
  };
16494
16742
  function withManualAreas(history, areas) {
@@ -16507,6 +16755,7 @@ var RESPAWN_LIMIT = Number(process.env.SWARM_RESPAWN_LIMIT) || 2;
16507
16755
  var RESPAWN_YOUNG_MS = Number(process.env.SWARM_RESPAWN_YOUNG_MS) || 60 * 1e3;
16508
16756
  var EXITED_NO_DONE_REASON = "\u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430, done() \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043D";
16509
16757
  var ABORTED_STOP_REASON = "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C, done() \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043D";
16758
+ var SUBMITTED_EXIT_TEXT = "\u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C, \u043D\u043E \u0440\u0430\u0431\u043E\u0442\u0430 \u0443\u0436\u0435 \u0441\u0434\u0430\u043D\u0430 (PR \u043E\u0442\u043A\u0440\u044B\u0442, done() \u043F\u0440\u0438\u043D\u044F\u0442) \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E \u0432 \u0440\u0435\u0432\u044C\u044E, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D";
16510
16759
  var EXIT_WORK_PUSHED = "\u0432\u0435\u0442\u043A\u0430 \u0435\u0441\u0442\u044C \u043D\u0430 origin \u2014 \u0435\u0441\u0442\u044C \u0447\u0442\u043E \u0441\u043F\u0430\u0441\u0430\u0442\u044C, PR \u043D\u0435 \u043E\u0442\u043A\u0440\u044B\u0442";
16511
16760
  var EXIT_WORK_LOST = "\u0432\u0435\u0442\u043A\u0438 \u043D\u0430 origin \u043D\u0435\u0442 \u2014 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u043E";
16512
16761
  var EXIT_WORK_UNKNOWN = "\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C origin \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u2014 \u0446\u0435\u043B\u0430 \u043B\u0438 \u0440\u0430\u0431\u043E\u0442\u0430, \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E";
@@ -16639,7 +16888,7 @@ function lastCiCheckMs(db, runId) {
16639
16888
  if (!evs.length) return null;
16640
16889
  return Date.parse(evs[evs.length - 1].at.replace(" ", "T") + "Z");
16641
16890
  }
16642
- function reviewPhaseInputFor(deps, run) {
16891
+ function reviewPhaseInputFor(deps, run, projectWantsVerify = true) {
16643
16892
  const events = deps.db.events(run.id);
16644
16893
  let ciRed = false;
16645
16894
  let ciFailing = [];
@@ -16664,20 +16913,35 @@ function reviewPhaseInputFor(deps, run) {
16664
16913
  const attempts = deps.db.reviewerAttemptCountFor(run.id);
16665
16914
  const lastAttempt = deps.db.lastReviewerAttemptFor(run.id);
16666
16915
  const reviewerDead = !reviewer && run.review_verdict == null && attempts >= REVIEW_SPAWN_LIMIT && (lastAttempt?.status === "failed" || lastAttempt?.status === "stalled" || lastAttempt?.status === "stopped");
16916
+ const verifyExpected = deps.spawnVerifier != null && projectWantsVerify;
16917
+ const verifierDead = verifyExpected && run.verify_verdict == null && deps.db.verifierCountFor(run.id) === 0 && deps.db.verifierAttemptCountFor(run.id) >= VERIFY_SPAWN_LIMIT;
16667
16918
  return {
16668
16919
  reviewVerdict: run.review_verdict,
16669
16920
  ciRed,
16670
16921
  ciFailing,
16671
16922
  ciProgress,
16672
16923
  reviewerName: reviewer?.name ?? null,
16673
- reviewerDead
16924
+ reviewerDead,
16925
+ verifyVerdict: run.verify_verdict,
16926
+ verifyExpected,
16927
+ verifierDead
16674
16928
  };
16675
16929
  }
16676
- function refreshReviewPhases(deps) {
16677
- for (const run of deps.db.listRuns("review")) {
16678
- if (run.reviews_run_id != null || run.resolves_run_id != null) continue;
16679
- if (!run.pr_url) continue;
16680
- const phase = computeReviewPhase(reviewPhaseInputFor(deps, run));
16930
+ async function refreshReviewPhases(deps) {
16931
+ const runs = deps.db.listRuns("review").filter((r) => r.reviews_run_id == null && r.resolves_run_id == null && !!r.pr_url);
16932
+ const wantsChecks = /* @__PURE__ */ new Map();
16933
+ const needsAnswer = (r) => deps.spawnVerifier != null && r.review_verdict === "approve" && r.verify_verdict == null;
16934
+ for (const run of runs) {
16935
+ if (!needsAnswer(run) || wantsChecks.has(run.repo)) continue;
16936
+ try {
16937
+ wantsChecks.set(run.repo, await projectWantsChecks(deps, run));
16938
+ } catch (e) {
16939
+ console.warn(`[overmind] autoReview \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (${run.repo}) \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D \u2014 \u0444\u0430\u0437\u0430 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043A\u0430\u043A \u0440\u0430\u043D\u044C\u0448\u0435:`, e);
16940
+ wantsChecks.set(run.repo, true);
16941
+ }
16942
+ }
16943
+ for (const run of runs) {
16944
+ const phase = computeReviewPhase(reviewPhaseInputFor(deps, run, wantsChecks.get(run.repo) ?? true));
16681
16945
  if (run.review_phase !== phase.label) deps.db.updateRun(run.id, { review_phase: phase.label });
16682
16946
  }
16683
16947
  }
@@ -16779,7 +17043,14 @@ async function retireOutdatedReviewers(deps, run) {
16779
17043
  narrate(deps, `${droneLabel(run)}: \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u0440\u043E\u0448\u043B\u043E\u0433\u043E \u043A\u0440\u0443\u0433\u0430 \u0435\u0449\u0451 \u0436\u0438\u0432 \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u0435\u0433\u043E, \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u043F\u043E\u0432\u0435\u0440\u0445 \u043D\u0435 \u0434\u0435\u0440\u0436\u0443.`);
16780
17044
  }
16781
17045
  }
16782
- async function finalizeMergedReview(deps, run) {
17046
+ async function finalizeMergedReview(deps, run, opts) {
17047
+ if (!opts?.workChecked) {
17048
+ const work = await taskWorkState(deps, run.task_slug, run.repo || "", run.pr_url ?? null);
17049
+ if (work.kind === "open") {
17050
+ noteOpenPrHold(deps, run, work);
17051
+ return false;
17052
+ }
17053
+ }
16783
17054
  await deps.finishTask(run.task_slug, run.workspace_id);
16784
17055
  if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
16785
17056
  });
@@ -16787,6 +17058,7 @@ async function finalizeMergedReview(deps, run) {
16787
17058
  deps.db.updateRun(run.id, { status: "done" });
16788
17059
  deps.db.addEvent(run.id, "done");
16789
17060
  await closeReviewersOnDeadPr(deps, run.id, "PR \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u0440\u0430\u043D\u044C\u0448\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E \u2014 \u0440\u0435\u0432\u044C\u044E \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E");
17061
+ return true;
16790
17062
  }
16791
17063
  async function finalizeClosedPrReview(deps, run, reason) {
16792
17064
  await closeReviewersOnDeadPr(deps, run.id, "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E");
@@ -16800,7 +17072,7 @@ async function prGateForReviewer(deps, run, prUrl, when) {
16800
17072
  const st = await deps.prStatus(prUrl).catch(() => null);
16801
17073
  switch (prLiveness(st)) {
16802
17074
  case "merged":
16803
- await finalizeMergedReview(deps, run);
17075
+ if (!await finalizeMergedReview(deps, run)) return { kind: "wait" };
16804
17076
  narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D, ${when} \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D, \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D.`);
16805
17077
  return { kind: "finalized" };
16806
17078
  case "closed":
@@ -16831,6 +17103,16 @@ function doneMergedPrForSlug(db, slug) {
16831
17103
  );
16832
17104
  return done?.pr_url ?? null;
16833
17105
  }
17106
+ var OPEN_PR_HOLD_MARK = "\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u043E\u0441\u0442\u0430\u043B\u0438\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 PR";
17107
+ async function taskWorkState(deps, slug, repoDir, candidate) {
17108
+ const openPrs = deps.openPrsForTask ? await deps.openPrsForTask(slug, repoDir).catch(() => null) : null;
17109
+ return decideTaskWork({ mergedCandidate: candidate, openPrs });
17110
+ }
17111
+ function noteOpenPrHold(deps, run, v) {
17112
+ if (deps.db.events(run.id).some((e) => (e.text ?? "").includes(OPEN_PR_HOLD_MARK))) return;
17113
+ deps.db.addEvent(run.id, "step", formatOpenPrHold(v));
17114
+ narrate(deps, `${droneLabel(run)} \u2014 \u0443 ${run.task_slug} \u0435\u0449\u0451 \u043E\u0442\u043A\u0440\u044B\u0442 PR \u0441 \u043D\u0435\u0432\u043B\u0438\u0442\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u043E\u0439 (${v.prs.join(", ")}), \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E.`);
17115
+ }
16834
17116
  async function closeDuplicateTaskRun(deps, run, mergedUrl) {
16835
17117
  if (run.pid != null && deps.kill && deps.isAlive(run.pid)) {
16836
17118
  try {
@@ -16840,7 +17122,7 @@ async function closeDuplicateTaskRun(deps, run, mergedUrl) {
16840
17122
  }
16841
17123
  if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
16842
17124
  });
16843
- const reason = `PR \u0437\u0430\u0434\u0430\u0447\u0438 \u0443\u0436\u0435 \u0441\u043C\u0435\u0440\u0436\u0435\u043D (${mergedUrl}) \u2014 \u043F\u0440\u043E\u0433\u043E\u043D-\u0434\u0443\u0431\u043B\u044C \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u0441\u043F\u0430\u0432\u043D\u0430/\u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0438 (TASWM-206)`;
17125
+ const reason = formatMergedDuplicateReason(mergedUrl);
16844
17126
  deps.db.updateRun(run.id, { status: "superseded", stop_reason: reason });
16845
17127
  deps.db.addEvent(run.id, "superseded", reason);
16846
17128
  }
@@ -16872,10 +17154,16 @@ async function closeMergedTaskRuns(deps) {
16872
17154
  mergedUrl = await deps.mergedPrForBranch(slug, group[0].repo || "").catch(() => null);
16873
17155
  }
16874
17156
  if (!mergedUrl) continue;
17157
+ const work = await taskWorkState(deps, slug, group[0].repo || "", mergedUrl);
17158
+ if (work.kind === "open") {
17159
+ for (const run of nonTerminal) noteOpenPrHold(deps, run, work);
17160
+ continue;
17161
+ }
17162
+ if (work.kind === "merged") mergedUrl = work.pr;
16875
17163
  const canonical = withPr && withPr.pr_url === mergedUrl && !isTerminalStatus(withPr.status) ? withPr : null;
16876
17164
  for (const run of nonTerminal) {
16877
17165
  if (canonical && run.id === canonical.id) {
16878
- await finalizeMergedReview(deps, run);
17166
+ await finalizeMergedReview(deps, run, { workChecked: true });
16879
17167
  narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u2014 ${slug} \u0437\u0430\u043A\u0440\u044B\u0442\u0430 (\u0434\u0443\u0431\u043B\u044C-\u0440\u0430\u043D\u044B \u0440\u043E\u044F, TASWM-206).`);
16880
17168
  } else {
16881
17169
  await closeDuplicateTaskRun(deps, run, mergedUrl);
@@ -17097,12 +17385,19 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
17097
17385
  // вторую ветку мимо PR. Пишем ту, что реально записана на прогоне; пусто (первый спавн —
17098
17386
  // ветку заводит spawnDrone из слага) — прежняя формулировка.
17099
17387
  `\u0422\u044B \u2014 \u0434\u0440\u043E\u043D \u0440\u043E\u044F. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0432 \u044D\u0442\u043E\u043C git-worktree, \u0432\u0435\u0442\u043A\u0430 \`${run.branch || run.task_slug}\`${run.branch && run.branch !== run.task_slug ? " (\u043E\u043D\u0430 \u041D\u0415 \u0440\u0430\u0432\u043D\u0430 \u0441\u043B\u0430\u0433\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0443\u0436\u0435 \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E PR, \u043F\u0443\u0448 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043D\u0435\u0451)" : " \u0441\u043E\u0437\u0434\u0430\u043D\u0430 \u043F\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438"}.`,
17100
- "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); report_acceptance_check(id, state, evidence) \u2014 \u0437\u0430\u043A\u0440\u043E\u0439 \u041A\u0410\u0416\u0414\u042B\u0419 \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 (id \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB, state = passed|failed|pending \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, evidence \u2014 \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B/\u0441\u0441\u044B\u043B\u043A\u0430/\u0447\u0442\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B); \u043F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u043D\u0438\u043C \u041F\u0415\u0420\u0415\u0414 done(), \u0438\u043D\u0430\u0447\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440; done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; return_task(missing, readings, assumed, questions) \u2014 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0421\u042B\u0420\u0423\u042E \u0437\u0430\u0434\u0430\u0447\u0443 \u0430\u0432\u0442\u043E\u0440\u0443 (\u0441\u043C. \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0438\u0436\u0435); note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
17388
+ "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); report_acceptance_check(id, state, evidence) \u2014 \u0437\u0430\u043A\u0440\u043E\u0439 \u041A\u0410\u0416\u0414\u042B\u0419 \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 (id \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB, state = passed|failed|pending \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, evidence \u2014 \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B/\u0441\u0441\u044B\u043B\u043A\u0430/\u0447\u0442\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B); \u043F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u043D\u0438\u043C \u041F\u0415\u0420\u0415\u0414 done(), \u0438\u043D\u0430\u0447\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440; done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; return_task(reason, missing, readings, assumed, questions) \u2014 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0421\u042B\u0420\u0423\u042E \u0437\u0430\u0434\u0430\u0447\u0443 \u0430\u0432\u0442\u043E\u0440\u0443 (\u0441\u043C. \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0438\u0436\u0435); note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
17101
17389
  // TASWM-412: третий выход из непонятной задачи. Раньше их было два, и оба плохие —
17102
17390
  // угадать постановку (сделает не то) или ask (слот занят, а ответить может только автор
17103
17391
  // задачи, не оператор роя). Граница здесь ОБЯЗАТЕЛЬНА: дрон, легко возвращающий задачи,
17104
17392
  // становится отписочником, а это хуже, чем дрон, который иногда угадывает неверно.
17105
- "\u0421\u042B\u0420\u0423\u042E \u0417\u0410\u0414\u0410\u0427\u0423 \u041D\u0415 \u0412\u042B\u0414\u0423\u041C\u042B\u0412\u0410\u0419 \u2014 \u0412\u0415\u0420\u041D\u0418 \u0410\u0412\u0422\u041E\u0420\u0423. \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0432 \u0437\u0430\u0434\u0430\u0447\u0443, \u043E\u0442\u0432\u0435\u0442\u044C \u0441\u0435\u0431\u0435 \u043D\u0430 \u0434\u0432\u0430 \u0432\u043E\u043F\u0440\u043E\u0441\u0430: \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u043B\u0438, \u0427\u0422\u041E \u043D\u0443\u0436\u043D\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C, \u0438 \u0435\u0441\u0442\u044C \u043B\u0438 \u0442\u043E, \u041F\u041E \u0427\u0415\u041C\u0423 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u041E\u0431\u0430 \xAB\u043D\u0435\u0442\xBB \u2192 return_task(missing, readings, assumed, questions): \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0443\u0435\u0434\u0443\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0430\u0432\u0442\u043E\u0440\u0443, \u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0439\u0434\u0451\u0442 \u043D\u0430 \u043D\u0435\u0433\u043E \u0438 \u0432 \u0440\u0430\u0431\u043E\u0442\u0443, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u043A\u0430\u043A \xAB\u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u0430 \u0430\u0432\u0442\u043E\u0440\u0443\xBB (\u043D\u0435 \xAB\u043F\u0440\u043E\u0432\u0430\u043B\u0435\u043D\u0430\xBB), \u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F. \u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044C \u041C\u041E\u0416\u041D\u041E, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043D\u0438 \u0432 \u043E\u0434\u043D\u043E\u043C \u0440\u0430\u0437\u0443\u043C\u043D\u043E\u043C \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0438 \u0438\u043B\u0438 \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0422\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0439 \u0434\u0432\u0430 \u0438 \u043E\u0431\u0430 \u043E\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B \u2014 \u0432\u044B\u0431\u0435\u0440\u0438 \u043E\u0447\u0435\u0432\u0438\u0434\u043D\u043E\u0435, \u0441\u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0430\u0437\u043E\u0432\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0443 \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438 PR. \u0417\u0430\u0434\u0430\u0447\u0430 \u043F\u043E\u043D\u044F\u0442\u043D\u0430, \u043D\u043E \u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u043D\u043A\u0442\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u044D\u0442\u043E ask, \u0430 \u043D\u0435 \u0432\u043E\u0437\u0432\u0440\u0430\u0442.",
17393
+ "\u0421\u042B\u0420\u0423\u042E \u0417\u0410\u0414\u0410\u0427\u0423 \u041D\u0415 \u0412\u042B\u0414\u0423\u041C\u042B\u0412\u0410\u0419 \u2014 \u0412\u0415\u0420\u041D\u0418 \u0410\u0412\u0422\u041E\u0420\u0423. \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0432 \u0437\u0430\u0434\u0430\u0447\u0443, \u043E\u0442\u0432\u0435\u0442\u044C \u0441\u0435\u0431\u0435 \u043D\u0430 \u0434\u0432\u0430 \u0432\u043E\u043F\u0440\u043E\u0441\u0430: \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u043B\u0438, \u0427\u0422\u041E \u043D\u0443\u0436\u043D\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C, \u0438 \u0435\u0441\u0442\u044C \u043B\u0438 \u0442\u043E, \u041F\u041E \u0427\u0415\u041C\u0423 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u041E\u0431\u0430 \xAB\u043D\u0435\u0442\xBB \u2192 return_task(reason, missing, readings, assumed, questions): \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0443\u0435\u0434\u0443\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0430\u0432\u0442\u043E\u0440\u0443, \u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0439\u0434\u0451\u0442 \u043D\u0430 \u043D\u0435\u0433\u043E \u0438 \u0432 \u0440\u0430\u0431\u043E\u0442\u0443, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u043A\u0430\u043A \xAB\u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u0430 \u0430\u0432\u0442\u043E\u0440\u0443\xBB (\u043D\u0435 \xAB\u043F\u0440\u043E\u0432\u0430\u043B\u0435\u043D\u0430\xBB), \u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F. \u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044C \u041C\u041E\u0416\u041D\u041E, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043D\u0438 \u0432 \u043E\u0434\u043D\u043E\u043C \u0440\u0430\u0437\u0443\u043C\u043D\u043E\u043C \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0438 \u0438\u043B\u0438 \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0422\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0439 \u0434\u0432\u0430 \u0438 \u043E\u0431\u0430 \u043E\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B \u2014 \u0432\u044B\u0431\u0435\u0440\u0438 \u043E\u0447\u0435\u0432\u0438\u0434\u043D\u043E\u0435, \u0441\u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0430\u0437\u043E\u0432\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0443 \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438 PR. \u0417\u0430\u0434\u0430\u0447\u0430 \u043F\u043E\u043D\u044F\u0442\u043D\u0430, \u043D\u043E \u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u043D\u043A\u0442\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u044D\u0442\u043E ask, \u0430 \u043D\u0435 \u0432\u043E\u0437\u0432\u0440\u0430\u0442. \u041F\u0440\u0438\u0447\u0438\u043D\u0443 \u043D\u0430\u0437\u043E\u0432\u0438 \u041A\u041E\u0414\u041E\u041C (reason): unclear_goal \u2014 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C; no_acceptance \u2014 \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442; too_big \u2014 \u0441\u043C. \u043D\u0438\u0436\u0435; contradiction \u2014 \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u0441\u0430\u043C\u0430 \u0441\u0435\u0431\u0435. \u041A\u043E\u0434 \u043C\u0435\u0440\u044F\u0435\u0442 \u043D\u0435 \u0442\u0435\u0431\u044F, \u0430 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u043E \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043E\u043A \u043F\u043E \u0431\u044D\u043A\u043B\u043E\u0433\u0443, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0441\u0442\u0430\u0432\u044C \u0447\u0435\u0441\u0442\u043D\u044B\u0439, \u0430 \u043D\u0435 \u0443\u0434\u043E\u0431\u043D\u044B\u0439.",
17394
+ // TASWM-505: ЕДИНСТВЕННЫЙ сорт негодной задачи, который не покрывался ничем. claimPolicy
17395
+ // (TASWM-357) отсеивает родителей с незакрытыми детьми, но лист на 8 часов уезжает дрону
17396
+ // целиком — и он упирается в потолок трат на середине, оставляя недоделанную работу
17397
+ // человеку. Это уже случалось. Подзадачи дрон при этом НЕ заводит: разбиение —
17398
+ // продуктовое решение, а плохое разбиение хуже никакого, потому что выглядит
17399
+ // авторитетно (заведённые подзадачи читаются как согласованный план).
17400
+ "\u0421\u041B\u0418\u0428\u041A\u041E\u041C \u0412\u0415\u041B\u0418\u041A\u0410 \u2014 \u0442\u043E\u0436\u0435 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 (reason=too_big). \u0417\u0430\u0434\u0430\u0447\u0430 \u043F\u043E\u043D\u044F\u0442\u043D\u0430 \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u0430, \u043D\u043E \u044D\u0442\u043E \u043D\u0435 \u0437\u0430\u0434\u0430\u0447\u0430, \u0430 \u043F\u0430\u0447\u043A\u0430 \u0437\u0430\u0434\u0430\u0447: \u0437\u0430 \u043E\u0434\u0438\u043D \u043F\u0440\u043E\u0433\u043E\u043D \u0435\u0451 \u043D\u0435 \u0434\u043E\u0432\u0435\u0441\u0442\u0438, \u0430 \u0443\u043F\u0435\u0440\u0435\u0442\u044C\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 \u043D\u0430 \u0441\u0435\u0440\u0435\u0434\u0438\u043D\u0435 \u0445\u0443\u0436\u0435, \u0447\u0435\u043C \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0441\u0440\u0430\u0437\u0443 \u2014 \u043D\u0435\u0434\u043E\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435 \u0434\u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443. \u041C\u0435\u0440\u0438\u043B\u043E \u043D\u0435 \xAB\u043C\u043D\u043E\u0433\u043E\u0432\u0430\u0442\u043E \u0440\u0430\u0431\u043E\u0442\u044B\xBB, \u0430 \xAB\u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0435 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043A\u0438 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u044C\xBB. \u0420\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435 \u043C\u043E\u0436\u0435\u0448\u044C \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0418\u0422\u042C \u0430\u0432\u0442\u043E\u0440\u0443 \u0441\u043F\u0438\u0441\u043A\u043E\u043C (\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 split \u0443 return_task) \u2014 \u043D\u043E \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438 \u041D\u0415 \u0437\u0430\u0432\u043E\u0434\u0438: \u0440\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435 \u0440\u0435\u0448\u0430\u0435\u0442 \u0430\u0432\u0442\u043E\u0440.",
17106
17401
  "\u041F\u0440\u0430\u0432\u0438\u043B\u0430: \u0442\u0440\u043E\u0433\u0430\u0435\u0448\u044C UI \u2014 \u0421\u041D\u0410\u0427\u0410\u041B\u0410 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u0435\u0441\u0442\u044C \u043B\u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0441\u043A\u0438\u043B\u043B \u0441 UI-\u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0438 \u0441\u043B\u0435\u0434\u0443\u0439 \u0435\u0433\u043E \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435 (\u043D\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0430 \u2014 \u0437\u0430\u0432\u0435\u0434\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443, \u043D\u0435 \u0445\u0430\u0440\u0434\u043A\u043E\u0434\u044C \u043E\u0434\u043D\u043E\u0440\u0430\u0437\u043E\u0432\u043E\u0435); \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u0441\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438; \u043F\u043E\u0441\u043B\u0435 UI-\u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043F\u0440\u0438\u043B\u043E\u0436\u0438 \u0441\u043A\u0440\u0438\u043D\u044B mobile+desktop \u041A \u0417\u0410\u0414\u0410\u0427\u0415 (\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 screenshots \u0443 done); \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0447\u0435\u0440\u0435\u0437 gh \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url).",
17107
17402
  // TASWM-377: у сессии есть ПОТОЛОК ТРАТ, и он режет её без предупреждения, прямо посреди
17108
17403
  // работы. Дрон об этом знать обязан: цена сессии растёт от ЧИСЛА ХОДОВ (каждый ход заново
@@ -17467,6 +17762,16 @@ async function wakeRun(deps, runId) {
17467
17762
  if (!run) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0437\u0435\u0440\u043A\u0430\u043B\u0435 \u0440\u043E\u044F` };
17468
17763
  if (run.pid != null && deps.isAlive(run.pid)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 (pid ${run.pid}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
17469
17764
  if (isTerminalStatus(run.status)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435\u043D (${run.status}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
17765
+ const authorSideStopped = deps.db.events(run.id).some(
17766
+ (e) => [RESOLVE_ESCALATION_MARK, CI_WAKE_ESCALATION_MARK, VERIFY_ESCALATION_MARK].some(
17767
+ (m) => (e.text ?? "").includes(m)
17768
+ )
17769
+ );
17770
+ const changesEscalated = deps.db.events(run.id).some((e) => (e.text ?? "").includes(`${CHANGES_WAKE_ESCALATION_MARK} (\u043A\u0440\u0443\u0433 ${run.review_cycle ?? 0})`));
17771
+ const stoppedForHuman = deliberateReviewStop(deps.db, run) != null || changesEscalated || authorSideStopped;
17772
+ if (run.status === "review" && run.pr_url && !needsFixIteration(run) && !stoppedForHuman) {
17773
+ return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0441\u0434\u0430\u043D \u043D\u0430 \u0440\u0435\u0432\u044C\u044E (${run.pr_url}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E; \u043F\u0440\u0430\u0432\u043A\u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u0440\u043E\u0439 \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443` };
17774
+ }
17470
17775
  const worktreeExists = deps.worktreeExists ?? existsSync18;
17471
17776
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
17472
17777
  const wt = run.worktree_path;
@@ -17915,15 +18220,17 @@ function effectiveRepo(deps, run) {
17915
18220
  async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback) {
17916
18221
  const model = await models.forRepo(repo);
17917
18222
  if (!hasZoneModel(model)) return { kind: "unknown" };
17918
- const live = db.listRuns().filter((r) => (r.kind ?? "drone") === "drone" && ACTIVE_DRONE_STATUSES.has(r.status));
17919
- activeFiles.prune(live.map((r) => r.id));
18223
+ const holders = db.listRuns().filter((r) => holdsFiles(r));
18224
+ activeFiles.prune(holders.map((r) => r.id));
17920
18225
  const active = [];
17921
- for (const r of live) {
18226
+ for (const r of holders) {
17922
18227
  if (r.id === run.id) continue;
17923
18228
  if (normalizeRepo(r.repo || repoFallback || "") !== repo) continue;
17924
- const files = await activeFiles.filesOf(r);
18229
+ const snapshot = r.changed_paths ?? [];
18230
+ const files = snapshot.length ? snapshot : await activeFiles.filesOf(r);
18231
+ const prUrl = r.status === "review" ? r.pr_url : null;
17925
18232
  active.push(
17926
- files.length ? { run: r, files, actual: true } : { run: r, files: predictForRun(model, r).map((p) => p.path), actual: false }
18233
+ files.length ? { run: r, files, actual: true, prUrl } : { run: r, files: predictForRun(model, r).map((p) => p.path), actual: false, prUrl }
17927
18234
  );
17928
18235
  }
17929
18236
  return decideConflict({ model, candidate: run, active });
@@ -18067,17 +18374,31 @@ async function trySpawnQueuedRun(deps, run) {
18067
18374
  // причина одна на машину — в лог уходит одной строкой, а не по строке на прогон
18068
18375
  });
18069
18376
  }
18070
- const spentAttempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
18377
+ const spentAttempts = attemptsFor(deps, run.task_slug);
18071
18378
  if (spentAttempts >= PRE_START_ATTEMPT_LIMIT) {
18072
18379
  const last = lastPreStartAttempt(deps.db.listRuns(), run.task_slug);
18073
- const why = last && isUnretriableParked(last) ? `${SPAWN_PARK_MARK}: \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043E\u0442\u043A\u0430\u0437\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u043E\u0432\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-485). \u0427\u0435\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C \u2014 \u0432 \u043F\u0440\u043E\u0433\u043E\u043D\u0435 #${last.id}` : `${PRE_START_MARK} ${spentAttempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-422)`;
18380
+ const cause = parkedCause(last);
18381
+ const tail2 = cause ? ` \u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E: ${cause}` : "";
18382
+ const why = (last && isUnretriableParked(last) ? `${SPAWN_PARK_MARK}: \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043E\u0442\u043A\u0430\u0437\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u043E\u0432\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-485). \u0427\u0435\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C \u2014 \u0432 \u043F\u0440\u043E\u0433\u043E\u043D\u0435 #${last.id}` : `${PRE_START_MARK} ${spentAttempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-422)`) + tail2;
18074
18383
  deps.db.updateRun(run.id, { status: "stopped", stop_reason: why });
18075
18384
  deps.db.addEvent(run.id, "step", why);
18385
+ if (deps.parkTask && last && !isParkApplied(last)) {
18386
+ const ok = await deps.parkTask(run, last.stop_reason ?? why, { comment: false }).catch(() => false);
18387
+ if (ok)
18388
+ deps.db.updateRun(last.id, {
18389
+ // Водораздел включает и ЭТОТ, только что закрытый гейтом прогон: он появился до
18390
+ // подтверждения, и принимать его за ручной возврат нельзя (code-review PR #984).
18391
+ stop_reason: withParkApplied(last.stop_reason ?? why, stamp(), lastRunIdFor(deps.db.listRuns(), run.task_slug))
18392
+ });
18393
+ }
18076
18394
  const d = noteDefer(deps.spawnDefer ??= /* @__PURE__ */ new Map(), `pre-start-exhausted:${run.task_slug}`, why, GATE_LOG_HEARTBEAT_TICKS);
18077
18395
  if (d.log) console.error(`[overmind] ${run.task_slug}: ${why}${d.changed ? "" : ` \u2014 \u0435\u0449\u0451 \u0432\u0438\u0441\u0438\u0442, \u0442\u0438\u043A ${d.ticks}`}`);
18078
18396
  return "closed";
18079
18397
  }
18080
- const mergedUrl = doneMergedPrForSlug(deps.db, run.task_slug) ?? (deps.mergedPrForBranch ? await deps.mergedPrForBranch(run.task_slug, run.repo || "").catch(() => null) : null);
18398
+ const mergedCandidate = doneMergedPrForSlug(deps.db, run.task_slug) ?? (deps.mergedPrForBranch ? await deps.mergedPrForBranch(run.task_slug, run.repo || "").catch(() => null) : null);
18399
+ const work = mergedCandidate ? await taskWorkState(deps, run.task_slug, run.repo || "", mergedCandidate) : null;
18400
+ if (work?.kind === "open") noteOpenPrHold(deps, run, work);
18401
+ const mergedUrl = work?.kind === "merged" ? work.pr : null;
18081
18402
  if (mergedUrl) {
18082
18403
  await closeDuplicateTaskRun(deps, run, mergedUrl);
18083
18404
  await deps.finishTask(run.task_slug, run.workspace_id).catch(() => {
@@ -18115,6 +18436,13 @@ async function trySpawnQueuedRun(deps, run) {
18115
18436
  if (logged.get(run.id) !== e.message) {
18116
18437
  logged.set(run.id, e.message);
18117
18438
  deps.db.addEvent(run.id, "step", `${e.message} \u2014 \u0437\u0430\u0434\u0430\u0447\u0430 \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 (TASWM-348)`);
18439
+ const blocker = deps.db.getRun(e.blockedByRunId);
18440
+ if (blocker)
18441
+ deps.db.addEvent(
18442
+ blocker.id,
18443
+ "step",
18444
+ `${run.task_slug} (#${run.id}) \u0441\u0442\u043E\u0438\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0438\u0437-\u0437\u0430 \u044D\u0442\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u2014 ${e.message} (TASWM-506)`
18445
+ );
18118
18446
  narrate(deps, `${droneLabel(run)} \u2014 ${e.message}. \u041D\u0435 \u0441\u043F\u0430\u0432\u043D\u044E \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u043E.`);
18119
18447
  }
18120
18448
  return "skipped";
@@ -18165,10 +18493,13 @@ function spawnRetryOpts(e) {
18165
18493
  if (!(e instanceof TaskBodyUnavailableError)) return {};
18166
18494
  return { retriable: TOOL_FAILURE_RETRIABLE[e.code] };
18167
18495
  }
18496
+ function attemptsFor(deps, taskSlug) {
18497
+ return preStartAttempts(deps.db.listRuns(), taskSlug, deps.startedAt != null ? { since: deps.startedAt } : {});
18498
+ }
18168
18499
  async function failBeforeStart(deps, run, reason, opts = {}) {
18169
18500
  deps.db.updateRun(run.id, { status: "failed", stop_reason: `${PRE_START_MARK}: ${reason}` });
18170
18501
  deps.db.addEvent(run.id, "failed", `${PRE_START_MARK} \u2014 ${reason}`);
18171
- const attempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
18502
+ const attempts = attemptsFor(deps, run.task_slug);
18172
18503
  if (opts.retriable !== false && attempts < PRE_START_ATTEMPT_LIMIT) {
18173
18504
  narrate(deps, `${droneLabel(run)} \u2014 ${PRE_START_MARK} (\u043F\u043E\u043F\u044B\u0442\u043A\u0430 ${attempts} \u0438\u0437 ${PRE_START_ATTEMPT_LIMIT}): ${reason}`);
18174
18505
  return "failed";
@@ -18179,7 +18510,13 @@ async function failBeforeStart(deps, run, reason, opts = {}) {
18179
18510
  narrate(deps, `${run.task_slug}: ${parked}`);
18180
18511
  if (deps.parkTask) {
18181
18512
  const ok = await deps.parkTask(run, parked).catch(() => false);
18182
- if (!ok) {
18513
+ if (ok)
18514
+ deps.db.updateRun(run.id, {
18515
+ // Водораздел (code-review PR #984) — самый большой id прогона задачи ПРЯМО СЕЙЧАС:
18516
+ // всё, что появится после него, заведено уже по возвращённой в очередь задаче.
18517
+ stop_reason: withParkApplied(parked, stamp(), lastRunIdFor(deps.db.listRuns(), run.task_slug))
18518
+ });
18519
+ else {
18183
18520
  deps.db.addEvent(
18184
18521
  run.id,
18185
18522
  "step",
@@ -18849,8 +19186,7 @@ async function tickOnce(deps) {
18849
19186
  const st = await deps.prStatus(prUrl);
18850
19187
  run = syncBranchFromPr(deps.db, run, st.headBranch);
18851
19188
  if (st.merged) {
18852
- await finalizeMergedReview(deps, run);
18853
- narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u2014 ${run.task_slug} \u0437\u0430\u043A\u0440\u044B\u0442\u0430, \u0441\u043B\u043E\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D.`);
19189
+ if (await finalizeMergedReview(deps, run)) narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u2014 ${run.task_slug} \u0437\u0430\u043A\u0440\u044B\u0442\u0430, \u0441\u043B\u043E\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D.`);
18854
19190
  continue;
18855
19191
  }
18856
19192
  if (prLiveness(st) === "closed") {
@@ -18994,7 +19330,7 @@ async function tickOnce(deps) {
18994
19330
  relinkUnboundReviewers(deps);
18995
19331
  await closeOrphanedReviewers(deps);
18996
19332
  await spawnVerifiersForOpenPrs(deps, stormActive);
18997
- refreshReviewPhases(deps);
19333
+ await refreshReviewPhases(deps);
18998
19334
  let liveChildPpids;
18999
19335
  try {
19000
19336
  liveChildPpids = deps.livePpids?.() ?? null;
@@ -19084,6 +19420,12 @@ async function tickOnce(deps) {
19084
19420
  }
19085
19421
  }
19086
19422
  if (!alive && (outcome === "graceful" || outcome === "aborted") && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && run.kind !== "hero") {
19423
+ if (!wasAborted(outcome) && run.pr_url) {
19424
+ deps.db.updateRun(run.id, { status: "review", stop_reason: null });
19425
+ deps.db.addEvent(run.id, "step", `${SUBMITTED_EXIT_TEXT}: ${run.pr_url}`);
19426
+ narrate(deps, `${droneLabel(run)} \u0432\u044B\u0448\u0435\u043B, \u043D\u043E \u0440\u0430\u0431\u043E\u0442\u0430 \u0443\u0436\u0435 \u0441\u0434\u0430\u043D\u0430 (${run.pr_url}) \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E \u0432 \u0440\u0435\u0432\u044C\u044E, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D.`);
19427
+ continue;
19428
+ }
19087
19429
  const aborted = wasAborted(outcome);
19088
19430
  const abort = aborted ? deps.exitAbort?.(run) ?? null : null;
19089
19431
  const what = aborted ? abortReason(abort) : EXITED_NO_DONE_REASON;
@@ -20350,6 +20692,9 @@ async function runDaemon(opts) {
20350
20692
  // Смерженный PR ветки-слага (TASWM-206) — тот же фолбэк на дефолт-репо роя, что и
20351
20693
  // openPrsForBranch выше; реальный gh, логика закрытия дублей — в tickOnce (тестируется).
20352
20694
  mergedPrForBranch: (branch, repoDir) => mergedPrForBranch(branch, repoDir || opts.repo),
20695
+ // ВСЕ открытые PR задачи (TASWM-515) — тот же фолбэк на дефолт-репо роя. Реальный gh;
20696
+ // и отбор «этот PR — задачи», и решение «закончена ли работа» чистые (swarm-core).
20697
+ openPrsForTask: (slug, repoDir) => openPrsForTask(slug, repoDir || opts.repo),
20353
20698
  // Закрыта ли задача в Taskless (TASWM-137 п.г): death-detect гасит зомби-прогон, чья
20354
20699
  // задача уже completed/cancelled/archived, вместо респавна. null — Taskless недоступен
20355
20700
  // (не гасим). Без tl (нет токена) ключ не задаём — шаг инертен, старое поведение.
@@ -20365,17 +20710,22 @@ async function runDaemon(opts) {
20365
20710
  // задачу), а не кодом возврата: клиент глушит отказ сервера внутрь (safeCall), и по
20366
20711
  // возврату «отложил» было бы неотличимо от «сказал, но не доехало» — то самое молчание,
20367
20712
  // из-за которого 17.08 круг и крутился.
20368
- parkTask: async (run, reason) => {
20713
+ parkTask: async (run, reason, opts2) => {
20369
20714
  const client = tlFor(run.workspace_id);
20370
20715
  if (!client) return false;
20371
- await client.addComment(
20372
- run.task_slug,
20373
- `${reason}
20716
+ if (opts2?.comment !== false)
20717
+ await client.addComment(
20718
+ run.task_slug,
20719
+ // TASWM-514: советуем ИМЕННО возврат в очередь — теперь этот рычаг работает
20720
+ // (возврат в «не начато» обнуляет счётчик попыток). И причина стоит в первой
20721
+ // строке (её несёт `reason`, см. parkReason): человек, читающий пометку через
20722
+ // час, должен из неё понять, ЧТО чинить, а не раскапывать записи прогонов.
20723
+ `${reason}
20374
20724
 
20375
- \u0417\u0430\u0434\u0430\u0447\u0430 \u0441\u043D\u044F\u0442\u0430 \u0441 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0440\u043E\u044F \u2014 \u043F\u043E\u043A\u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043D\u0435 \u0443\u0441\u0442\u0440\u0430\u043D\u0435\u043D\u0430, \u0431\u0440\u0430\u0442\u044C \u0435\u0451 \u0441\u043D\u043E\u0432\u0430 \u0441\u043C\u044B\u0441\u043B\u0430 \u043D\u0435\u0442. \u0420\u0430\u0437\u0431\u0435\u0440\u0438\u0441\u044C \u0438 \u0432\u0435\u0440\u043D\u0438 \u0432 \u0431\u044D\u043A\u043B\u043E\u0433.`,
20376
- { swarmKind: "report", swarmDroneName: run.name }
20377
- ).catch(() => {
20378
- });
20725
+ \u0417\u0430\u0434\u0430\u0447\u0430 \u0441\u043D\u044F\u0442\u0430 \u0441 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0440\u043E\u044F \u2014 \u043F\u043E\u043A\u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043D\u0435 \u0443\u0441\u0442\u0440\u0430\u043D\u0435\u043D\u0430, \u0431\u0440\u0430\u0442\u044C \u0435\u0451 \u0441\u043D\u043E\u0432\u0430 \u0441\u043C\u044B\u0441\u043B\u0430 \u043D\u0435\u0442. \u0423\u0441\u0442\u0440\u0430\u043D\u0438 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0438 \u0432\u0435\u0440\u043D\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0432 \xAB\u043D\u0435 \u043D\u0430\u0447\u0430\u0442\u043E\xBB \u2014 \u0440\u043E\u0439 \u043F\u043E\u0434\u0445\u0432\u0430\u0442\u0438\u0442 \u0435\u0451 \u0441\u043D\u043E\u0432\u0430.`,
20726
+ { swarmKind: "report", swarmDroneName: run.name }
20727
+ ).catch(() => {
20728
+ });
20379
20729
  await client.startTask(run.task_slug).catch(() => {
20380
20730
  });
20381
20731
  const after = await client.getTask(run.task_slug).catch(() => null);
@@ -20589,6 +20939,9 @@ async function runDaemon(opts) {
20589
20939
  nudge
20590
20940
  ),
20591
20941
  worktreeExists: (p) => existsSync18(p),
20942
+ // TASWM-514: с этого момента считаем попытки спавна — счётчик живёт ровно одну жизнь
20943
+ // процесса демона и рестарт не переживает (см. preStartAttempts).
20944
+ startedAt: Date.now(),
20592
20945
  hasTranscript: executorHasSession,
20593
20946
  // CI-осведомлённость review-PR (TASWM-102): реальный `gh pr checks`/`gh run view --log-failed`
20594
20947
  // (gh.ts) — решение (будить ли дрона) и гейты живут в tickOnce/handleCiRed. failureSummary
@@ -21050,6 +21403,7 @@ export {
21050
21403
  GATE_LOG_HEARTBEAT_TICKS,
21051
21404
  HEADLESS_BLOCK,
21052
21405
  HERO_CONTINUE_NUDGE,
21406
+ OPEN_PR_HOLD_MARK,
21053
21407
  OVERMIND_ENTRY,
21054
21408
  RESOLVE_ESCALATION_MARK,
21055
21409
  RESOLVE_LIMIT,
@@ -21067,6 +21421,8 @@ export {
21067
21421
  STALL_FLOOR_MS,
21068
21422
  STALL_MS,
21069
21423
  STORM_DEAD_LIMIT,
21424
+ SUBMITTED_EXIT_TEXT,
21425
+ VERIFY_ESCALATION_MARK,
21070
21426
  VERIFY_SPAWN_LIMIT,
21071
21427
  WATCHDOG_PARK_REASON,
21072
21428
  abortReason,
@@ -21099,5 +21455,6 @@ export {
21099
21455
  tickOnce,
21100
21456
  verdictCommentMark,
21101
21457
  wakeRun,
21102
- worktreeFromCmd
21458
+ worktreeFromCmd,
21459
+ zoneConflictFor
21103
21460
  };