@taskless-app/swarm 0.6.25 → 0.6.26

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.
package/dist/cli.js CHANGED
@@ -51,10 +51,12 @@ function isMilestoneKind(kind) {
51
51
  function toRunKind(role) {
52
52
  return role === "hero" || role === "reviewer" || role === "verifier" ? role : "drone";
53
53
  }
54
- var EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
54
+ var ACTIVE_DRONE_STATUSES, FILE_HOLDING_STATUSES, EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
55
55
  var init_types = __esm({
56
56
  "../swarm-core/src/types.ts"() {
57
57
  "use strict";
58
+ ACTIVE_DRONE_STATUSES = /* @__PURE__ */ new Set(["running", "blocked"]);
59
+ FILE_HOLDING_STATUSES = /* @__PURE__ */ new Set([...ACTIVE_DRONE_STATUSES, "review"]);
58
60
  EVENT_KINDS = [
59
61
  "spawned",
60
62
  "step",
@@ -608,6 +610,24 @@ var init_prRef = __esm({
608
610
  }
609
611
  });
610
612
 
613
+ // ../swarm-core/src/returnReason.ts
614
+ function isReturnReason(v) {
615
+ return typeof v === "string" && RETURN_REASON_SET.has(v);
616
+ }
617
+ function toReturnReason(v) {
618
+ return isReturnReason(v) ? v : null;
619
+ }
620
+ var RETURN_REASONS, RETURN_REASON_SET, UNRETURNED_FAILURE_STATUSES, UNRETURNED_FAILURE_SET;
621
+ var init_returnReason = __esm({
622
+ "../swarm-core/src/returnReason.ts"() {
623
+ "use strict";
624
+ RETURN_REASONS = ["unclear_goal", "no_acceptance", "too_big", "contradiction"];
625
+ RETURN_REASON_SET = new Set(RETURN_REASONS);
626
+ UNRETURNED_FAILURE_STATUSES = ["failed", "blocked"];
627
+ UNRETURNED_FAILURE_SET = new Set(UNRETURNED_FAILURE_STATUSES);
628
+ }
629
+ });
630
+
611
631
  // ../swarm-core/src/modelPolicy.ts
612
632
  function findProvider(id) {
613
633
  const key = typeof id === "string" ? id.trim().toLowerCase() : "";
@@ -1610,6 +1630,7 @@ var init_src = __esm({
1610
1630
  init_mergeQueue();
1611
1631
  init_prRef();
1612
1632
  init_verifyVerdict();
1633
+ init_returnReason();
1613
1634
  init_modelPolicy();
1614
1635
  init_status();
1615
1636
  init_deps();
@@ -3019,7 +3040,7 @@ var init_package = __esm({
3019
3040
  "package.json"() {
3020
3041
  package_default = {
3021
3042
  name: "@taskless-app/swarm",
3022
- version: "0.6.25",
3043
+ version: "0.6.26",
3023
3044
  type: "module",
3024
3045
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3025
3046
  keywords: [
@@ -3070,7 +3091,7 @@ var init_package = __esm({
3070
3091
  execa: "^10.0.0",
3071
3092
  zod: "3.25.76"
3072
3093
  },
3073
- gitHead: "36aaed0414a46ea272ad762b81e051fecec40eaa"
3094
+ gitHead: "52416995d46b0a843374d14bfb00dd310f71c4bf"
3074
3095
  };
3075
3096
  }
3076
3097
  });
@@ -4435,9 +4456,14 @@ function rowToRun(row) {
4435
4456
  tokens_out: row.tokens_out ?? 0,
4436
4457
  duration_ms: row.duration_ms ?? 0,
4437
4458
  pr_url: row.pr_url ?? null,
4459
+ // TASWM-506: файлы, занятые прогоном с открытым PR — JSON-массив, как depends_on.
4460
+ changed_paths: row.changed_paths ? JSON.parse(row.changed_paths) : [],
4438
4461
  superseded_by: row.superseded_by ?? null,
4439
4462
  decision: row.decision ? JSON.parse(row.decision) : null,
4440
4463
  stop_reason: row.stop_reason ?? null,
4464
+ // TASWM-505: через узкий парсер, как verify_verdict ниже — в базе может лежать код,
4465
+ // записанный демоном другой версии; непонятное здесь значит «кода нет».
4466
+ return_reason: toReturnReason(row.return_reason),
4441
4467
  respawns: row.respawns ?? 0,
4442
4468
  // старые прогоны без колонки → 0
4443
4469
  resolves_run_id: row.resolves_run_id ?? null,
@@ -4512,6 +4538,7 @@ CREATE TABLE IF NOT EXISTS runs (
4512
4538
  superseded_by INTEGER,
4513
4539
  decision TEXT,
4514
4540
  stop_reason TEXT,
4541
+ return_reason TEXT,
4515
4542
  respawns INTEGER NOT NULL DEFAULT 0,
4516
4543
  resolves_run_id INTEGER,
4517
4544
  reviews_run_id INTEGER,
@@ -4628,7 +4655,16 @@ CREATE TABLE IF NOT EXISTS imports (
4628
4655
  // TASWM-451: чем проверять работу этого прогона — модель ревью, приехавшая с задачи.
4629
4656
  // Живёт на прогоне ИСПОЛНИТЕЛЯ: ревьюер и верификатор заводятся локально и читают её
4630
4657
  // оттуда, а не ходят за задачей в прод в момент спавна.
4631
- "ALTER TABLE runs ADD COLUMN review_model TEXT"
4658
+ "ALTER TABLE runs ADD COLUMN review_model TEXT",
4659
+ // TASWM-505: КОД причины возврата задачи автору (unclear_goal|no_acceptance|too_big|
4660
+ // contradiction) рядом с прозой в stop_reason — из него складывается распределение
4661
+ // «чего в постановках не хватает чаще всего». NULL — прогон не возвращали либо
4662
+ // возврат сделан демоном до этой фичи.
4663
+ "ALTER TABLE runs ADD COLUMN return_reason TEXT",
4664
+ // TASWM-506: файлы, которые держит прогон с открытым PR (JSON-массив, как depends_on).
4665
+ // Снимается в момент сдачи с PR: worktree сносится на мерже, и пересчитать список
4666
+ // потом неоткуда.
4667
+ "ALTER TABLE runs ADD COLUMN changed_paths TEXT"
4632
4668
  ]) {
4633
4669
  try {
4634
4670
  this.db.exec(alter);
@@ -4774,7 +4810,7 @@ CREATE TABLE IF NOT EXISTS imports (
4774
4810
  const set = fields.map((f) => `${f} = ?`).join(", ");
4775
4811
  const vals = fields.map((f) => {
4776
4812
  const v = patch[f];
4777
- if (f === "depends_on") {
4813
+ if (f === "depends_on" || f === "changed_paths") {
4778
4814
  const arr = v;
4779
4815
  return arr && arr.length ? JSON.stringify(arr) : null;
4780
4816
  }
@@ -7180,9 +7216,11 @@ var SwarmClient = class _SwarmClient {
7180
7216
  if ("duration_ms" in patch && patch.duration_ms !== void 0) out.durationMs = patch.duration_ms;
7181
7217
  if ("respawns" in patch && patch.respawns !== void 0) out.respawns = patch.respawns;
7182
7218
  if ("pr_url" in patch) out.prUrl = patch.pr_url ?? null;
7219
+ if ("changed_paths" in patch && patch.changed_paths) out.changedPaths = patch.changed_paths;
7183
7220
  if ("branch" in patch) out.branch = patch.branch ?? null;
7184
7221
  if ("repo" in patch) out.repo = patch.repo ?? null;
7185
7222
  if ("stop_reason" in patch) out.stopReason = patch.stop_reason ?? null;
7223
+ if ("return_reason" in patch) out.returnReason = patch.return_reason ?? null;
7186
7224
  if ("depends_on" in patch && patch.depends_on) out.dependsOn = patch.depends_on;
7187
7225
  if ("decision" in patch) out.decision = patch.decision ? JSON.stringify(patch.decision) : null;
7188
7226
  if ("kind" in patch && patch.kind !== void 0) out.role = patch.kind;
@@ -7258,9 +7296,12 @@ var SwarmClient = class _SwarmClient {
7258
7296
  tokens_out: 0,
7259
7297
  duration_ms: 0,
7260
7298
  pr_url: null,
7299
+ changed_paths: [],
7300
+ // файлы прогона снимаются при открытии PR (TASWM-506)
7261
7301
  superseded_by: null,
7262
7302
  decision: input.decision ?? null,
7263
7303
  stop_reason: null,
7304
+ return_reason: null,
7264
7305
  respawns: 0,
7265
7306
  resolves_run_id: input.resolves_run_id ?? null,
7266
7307
  reviews_run_id: input.reviews_run_id ?? null,
@@ -8862,9 +8903,12 @@ var SwarmClient = class _SwarmClient {
8862
8903
  tokens_out: 0,
8863
8904
  duration_ms: 0,
8864
8905
  pr_url: null,
8906
+ changed_paths: [],
8907
+ // занятые файлы (TASWM-506) — прод-поле, заполняется ниже
8865
8908
  superseded_by: null,
8866
8909
  decision: null,
8867
8910
  stop_reason: null,
8911
+ return_reason: null,
8868
8912
  respawns: 0,
8869
8913
  resolves_run_id: null,
8870
8914
  reviews_run_id: null,
@@ -8920,6 +8964,7 @@ var SwarmClient = class _SwarmClient {
8920
8964
  const verProd = pr.verifiesRunId ?? null;
8921
8965
  if (verProd) local.verifies_run_id = this.localIdForProdId(verProd) ?? local.verifies_run_id;
8922
8966
  local.verify_verdict = toVerifyVerdict(pr.verifyVerdict) ?? local.verify_verdict;
8967
+ local.return_reason = toReturnReason(pr.returnReason) ?? local.return_reason;
8923
8968
  local.review_phase = pr.reviewPhase ?? local.review_phase;
8924
8969
  local.repo = this.resolveRepo(pr.repo ?? null);
8925
8970
  local.host_id = pr.hostId ?? null;
@@ -8939,6 +8984,7 @@ var SwarmClient = class _SwarmClient {
8939
8984
  local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
8940
8985
  local.duration_ms = typeof pr.durationMs === "number" ? pr.durationMs : 0;
8941
8986
  local.pr_url = pr.prUrl ?? null;
8987
+ local.changed_paths = Array.isArray(pr.changedPaths) ? pr.changedPaths : local.changed_paths ?? [];
8942
8988
  local.stop_reason = pr.stopReason ?? null;
8943
8989
  local.respawns = typeof pr.respawns === "number" ? pr.respawns : 0;
8944
8990
  local.decision = parseDecision(pr.decision);
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",
@@ -575,6 +581,36 @@ var init_prRef = __esm({
575
581
  }
576
582
  });
577
583
 
584
+ // ../swarm-core/src/returnReason.ts
585
+ function isReturnReason(v) {
586
+ return typeof v === "string" && RETURN_REASON_SET.has(v);
587
+ }
588
+ function toReturnReason(v) {
589
+ return isReturnReason(v) ? v : null;
590
+ }
591
+ function returnReasonLabel(r) {
592
+ switch (r) {
593
+ case "unclear_goal":
594
+ return "\u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C";
595
+ case "no_acceptance":
596
+ return "\u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442";
597
+ case "too_big":
598
+ 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";
599
+ case "contradiction":
600
+ 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";
601
+ }
602
+ }
603
+ var RETURN_REASONS, RETURN_REASON_SET, UNRETURNED_FAILURE_STATUSES, UNRETURNED_FAILURE_SET;
604
+ var init_returnReason = __esm({
605
+ "../swarm-core/src/returnReason.ts"() {
606
+ "use strict";
607
+ RETURN_REASONS = ["unclear_goal", "no_acceptance", "too_big", "contradiction"];
608
+ RETURN_REASON_SET = new Set(RETURN_REASONS);
609
+ UNRETURNED_FAILURE_STATUSES = ["failed", "blocked"];
610
+ UNRETURNED_FAILURE_SET = new Set(UNRETURNED_FAILURE_STATUSES);
611
+ }
612
+ });
613
+
578
614
  // ../swarm-core/src/modelPolicy.ts
579
615
  function findProvider(id) {
580
616
  const key = typeof id === "string" ? id.trim().toLowerCase() : "";
@@ -1684,6 +1720,7 @@ var init_src = __esm({
1684
1720
  init_mergeQueue();
1685
1721
  init_prRef();
1686
1722
  init_verifyVerdict();
1723
+ init_returnReason();
1687
1724
  init_modelPolicy();
1688
1725
  init_status();
1689
1726
  init_deps();
@@ -3962,6 +3999,12 @@ function invalidPatchValue(field, v) {
3962
3999
  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
4000
  case "pr_url":
3964
4001
  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";
4002
+ case "changed_paths": {
4003
+ 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";
4004
+ 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})`;
4005
+ const badPath = v.find((p) => typeof p !== "string" || !p || p.length > CHANGED_PATH_LEN_MAX);
4006
+ 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)}`;
4007
+ }
3965
4008
  default:
3966
4009
  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
4010
  }
@@ -4215,7 +4258,7 @@ function channelDroneStore(opts) {
4215
4258
  openQuestionsFor: async () => (await call("openQuestionsFor")).questions
4216
4259
  };
4217
4260
  }
4218
- var LOCAL_CLI_KEY, PATCHABLE, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
4261
+ var LOCAL_CLI_KEY, PATCHABLE, CHANGED_PATHS_MAX, CHANGED_PATH_LEN_MAX, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
4219
4262
  var init_droneChannel = __esm({
4220
4263
  "src/droneChannel.ts"() {
4221
4264
  "use strict";
@@ -4229,8 +4272,16 @@ var init_droneChannel = __esm({
4229
4272
  // и пишется он тем же путём на ПРОВЕРЯЕМЫЙ прогон. Без него роль verifier была немой:
4230
4273
  // вердикт выносился комментарием на PR, а `done` отбивался каналом.
4231
4274
  "verify_verdict",
4232
- "review_cycle"
4275
+ "review_cycle",
4276
+ // TASWM-506: файлы, которые прогон занял своим PR. Едут тем же вызовом, что и сдача
4277
+ // (done → status=review + pr_url): считает их НАШ код в дрон-сервере (`changedPaths` по
4278
+ // worktree), а не модель — со стороны канала это такой же самоотчёт прогона о себе, как
4279
+ // pr_url. Потолки на размер — ниже, в invalidPatchValue: список идёт в гейт раздачи, и
4280
+ // сорвавшийся дрон не должен уметь заявить занятым весь репозиторий.
4281
+ "changed_paths"
4233
4282
  ]);
4283
+ CHANGED_PATHS_MAX = 1e3;
4284
+ CHANGED_PATH_LEN_MAX = 1024;
4234
4285
  EVENT_KIND_SET = new Set(EVENT_KINDS);
4235
4286
  ChannelUnavailableError = class extends Error {
4236
4287
  constructor(downMs, message) {
@@ -5541,9 +5592,14 @@ function rowToRun(row) {
5541
5592
  tokens_out: row.tokens_out ?? 0,
5542
5593
  duration_ms: row.duration_ms ?? 0,
5543
5594
  pr_url: row.pr_url ?? null,
5595
+ // TASWM-506: файлы, занятые прогоном с открытым PR — JSON-массив, как depends_on.
5596
+ changed_paths: row.changed_paths ? JSON.parse(row.changed_paths) : [],
5544
5597
  superseded_by: row.superseded_by ?? null,
5545
5598
  decision: row.decision ? JSON.parse(row.decision) : null,
5546
5599
  stop_reason: row.stop_reason ?? null,
5600
+ // TASWM-505: через узкий парсер, как verify_verdict ниже — в базе может лежать код,
5601
+ // записанный демоном другой версии; непонятное здесь значит «кода нет».
5602
+ return_reason: toReturnReason(row.return_reason),
5547
5603
  respawns: row.respawns ?? 0,
5548
5604
  // старые прогоны без колонки → 0
5549
5605
  resolves_run_id: row.resolves_run_id ?? null,
@@ -5618,6 +5674,7 @@ CREATE TABLE IF NOT EXISTS runs (
5618
5674
  superseded_by INTEGER,
5619
5675
  decision TEXT,
5620
5676
  stop_reason TEXT,
5677
+ return_reason TEXT,
5621
5678
  respawns INTEGER NOT NULL DEFAULT 0,
5622
5679
  resolves_run_id INTEGER,
5623
5680
  reviews_run_id INTEGER,
@@ -5734,7 +5791,16 @@ CREATE TABLE IF NOT EXISTS imports (
5734
5791
  // TASWM-451: чем проверять работу этого прогона — модель ревью, приехавшая с задачи.
5735
5792
  // Живёт на прогоне ИСПОЛНИТЕЛЯ: ревьюер и верификатор заводятся локально и читают её
5736
5793
  // оттуда, а не ходят за задачей в прод в момент спавна.
5737
- "ALTER TABLE runs ADD COLUMN review_model TEXT"
5794
+ "ALTER TABLE runs ADD COLUMN review_model TEXT",
5795
+ // TASWM-505: КОД причины возврата задачи автору (unclear_goal|no_acceptance|too_big|
5796
+ // contradiction) рядом с прозой в stop_reason — из него складывается распределение
5797
+ // «чего в постановках не хватает чаще всего». NULL — прогон не возвращали либо
5798
+ // возврат сделан демоном до этой фичи.
5799
+ "ALTER TABLE runs ADD COLUMN return_reason TEXT",
5800
+ // TASWM-506: файлы, которые держит прогон с открытым PR (JSON-массив, как depends_on).
5801
+ // Снимается в момент сдачи с PR: worktree сносится на мерже, и пересчитать список
5802
+ // потом неоткуда.
5803
+ "ALTER TABLE runs ADD COLUMN changed_paths TEXT"
5738
5804
  ]) {
5739
5805
  try {
5740
5806
  this.db.exec(alter);
@@ -5880,7 +5946,7 @@ CREATE TABLE IF NOT EXISTS imports (
5880
5946
  const set = fields.map((f) => `${f} = ?`).join(", ");
5881
5947
  const vals = fields.map((f) => {
5882
5948
  const v = patch[f];
5883
- if (f === "depends_on") {
5949
+ if (f === "depends_on" || f === "changed_paths") {
5884
5950
  const arr = v;
5885
5951
  return arr && arr.length ? JSON.stringify(arr) : null;
5886
5952
  }
@@ -7088,9 +7154,11 @@ var SwarmClient = class _SwarmClient {
7088
7154
  if ("duration_ms" in patch && patch.duration_ms !== void 0) out.durationMs = patch.duration_ms;
7089
7155
  if ("respawns" in patch && patch.respawns !== void 0) out.respawns = patch.respawns;
7090
7156
  if ("pr_url" in patch) out.prUrl = patch.pr_url ?? null;
7157
+ if ("changed_paths" in patch && patch.changed_paths) out.changedPaths = patch.changed_paths;
7091
7158
  if ("branch" in patch) out.branch = patch.branch ?? null;
7092
7159
  if ("repo" in patch) out.repo = patch.repo ?? null;
7093
7160
  if ("stop_reason" in patch) out.stopReason = patch.stop_reason ?? null;
7161
+ if ("return_reason" in patch) out.returnReason = patch.return_reason ?? null;
7094
7162
  if ("depends_on" in patch && patch.depends_on) out.dependsOn = patch.depends_on;
7095
7163
  if ("decision" in patch) out.decision = patch.decision ? JSON.stringify(patch.decision) : null;
7096
7164
  if ("kind" in patch && patch.kind !== void 0) out.role = patch.kind;
@@ -7166,9 +7234,12 @@ var SwarmClient = class _SwarmClient {
7166
7234
  tokens_out: 0,
7167
7235
  duration_ms: 0,
7168
7236
  pr_url: null,
7237
+ changed_paths: [],
7238
+ // файлы прогона снимаются при открытии PR (TASWM-506)
7169
7239
  superseded_by: null,
7170
7240
  decision: input.decision ?? null,
7171
7241
  stop_reason: null,
7242
+ return_reason: null,
7172
7243
  respawns: 0,
7173
7244
  resolves_run_id: input.resolves_run_id ?? null,
7174
7245
  reviews_run_id: input.reviews_run_id ?? null,
@@ -8770,9 +8841,12 @@ var SwarmClient = class _SwarmClient {
8770
8841
  tokens_out: 0,
8771
8842
  duration_ms: 0,
8772
8843
  pr_url: null,
8844
+ changed_paths: [],
8845
+ // занятые файлы (TASWM-506) — прод-поле, заполняется ниже
8773
8846
  superseded_by: null,
8774
8847
  decision: null,
8775
8848
  stop_reason: null,
8849
+ return_reason: null,
8776
8850
  respawns: 0,
8777
8851
  resolves_run_id: null,
8778
8852
  reviews_run_id: null,
@@ -8828,6 +8902,7 @@ var SwarmClient = class _SwarmClient {
8828
8902
  const verProd = pr.verifiesRunId ?? null;
8829
8903
  if (verProd) local.verifies_run_id = this.localIdForProdId(verProd) ?? local.verifies_run_id;
8830
8904
  local.verify_verdict = toVerifyVerdict(pr.verifyVerdict) ?? local.verify_verdict;
8905
+ local.return_reason = toReturnReason(pr.returnReason) ?? local.return_reason;
8831
8906
  local.review_phase = pr.reviewPhase ?? local.review_phase;
8832
8907
  local.repo = this.resolveRepo(pr.repo ?? null);
8833
8908
  local.host_id = pr.hostId ?? null;
@@ -8847,6 +8922,7 @@ var SwarmClient = class _SwarmClient {
8847
8922
  local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
8848
8923
  local.duration_ms = typeof pr.durationMs === "number" ? pr.durationMs : 0;
8849
8924
  local.pr_url = pr.prUrl ?? null;
8925
+ local.changed_paths = Array.isArray(pr.changedPaths) ? pr.changedPaths : local.changed_paths ?? [];
8850
8926
  local.stop_reason = pr.stopReason ?? null;
8851
8927
  local.respawns = typeof pr.respawns === "number" ? pr.respawns : 0;
8852
8928
  local.decision = parseDecision(pr.decision);
@@ -11472,6 +11548,206 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11472
11548
  import { z } from "zod";
11473
11549
  init_git();
11474
11550
 
11551
+ // src/diff.ts
11552
+ import { statSync as statSync10 } from "fs";
11553
+ import { join as join25 } from "path";
11554
+ import { $ as $4 } from "execa";
11555
+ var MAX_FILES = 200;
11556
+ var MAX_LINES_PER_FILE = 1500;
11557
+ var MAX_UNTRACKED = 60;
11558
+ function cleanPath(raw) {
11559
+ let p = raw.trim();
11560
+ if (p.startsWith('"') && p.endsWith('"') && p.length >= 2) p = p.slice(1, -1);
11561
+ return p;
11562
+ }
11563
+ function stripAB(raw) {
11564
+ const p = cleanPath(raw);
11565
+ if (p === "/dev/null") return p;
11566
+ if (p.startsWith("a/") || p.startsWith("b/")) return p.slice(2);
11567
+ return p;
11568
+ }
11569
+ function parseUnifiedDiff(text) {
11570
+ const files = [];
11571
+ if (!text) return files;
11572
+ const lines = text.split("\n");
11573
+ let f = null;
11574
+ let hunk = null;
11575
+ let oldNo = 0;
11576
+ let newNo = 0;
11577
+ const push = () => {
11578
+ if (f) files.push(f);
11579
+ };
11580
+ for (const line of lines) {
11581
+ if (line.startsWith("diff --git ")) {
11582
+ push();
11583
+ hunk = null;
11584
+ const m = line.match(/^diff --git (.+) (.+)$/);
11585
+ const guess = m ? stripAB(m[2]) : "";
11586
+ f = { path: guess, oldPath: null, status: "modified", added: 0, removed: 0, binary: false, hunks: [] };
11587
+ continue;
11588
+ }
11589
+ if (!f) continue;
11590
+ if (line.startsWith("new file mode")) {
11591
+ f.status = "added";
11592
+ continue;
11593
+ }
11594
+ if (line.startsWith("deleted file mode")) {
11595
+ f.status = "deleted";
11596
+ continue;
11597
+ }
11598
+ if (line.startsWith("rename from ") || line.startsWith("copy from ")) {
11599
+ f.oldPath = cleanPath(line.replace(/^(rename|copy) from /, ""));
11600
+ f.status = "renamed";
11601
+ continue;
11602
+ }
11603
+ if (line.startsWith("rename to ") || line.startsWith("copy to ")) {
11604
+ f.path = cleanPath(line.replace(/^(rename|copy) to /, ""));
11605
+ f.status = "renamed";
11606
+ continue;
11607
+ }
11608
+ if (line.startsWith("Binary files ")) {
11609
+ f.binary = true;
11610
+ continue;
11611
+ }
11612
+ if (line.startsWith("--- ")) {
11613
+ const p = stripAB(line.slice(4));
11614
+ if (p === "/dev/null") f.status = "added";
11615
+ continue;
11616
+ }
11617
+ if (line.startsWith("+++ ")) {
11618
+ const p = stripAB(line.slice(4));
11619
+ if (p === "/dev/null") f.status = "deleted";
11620
+ else if (p) f.path = p;
11621
+ continue;
11622
+ }
11623
+ if (line.startsWith("@@")) {
11624
+ const m = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@(.*)$/);
11625
+ if (m) {
11626
+ oldNo = Number(m[1]);
11627
+ newNo = Number(m[2]);
11628
+ hunk = { header: (m[3] ?? "").trim(), lines: [] };
11629
+ f.hunks.push(hunk);
11630
+ }
11631
+ continue;
11632
+ }
11633
+ if (!hunk) continue;
11634
+ if (line.startsWith("\\")) continue;
11635
+ const tag = line[0];
11636
+ const body = line.slice(1);
11637
+ if (tag === "+") {
11638
+ hunk.lines.push({ k: "add", o: null, n: newNo++, t: body });
11639
+ f.added++;
11640
+ } else if (tag === "-") {
11641
+ hunk.lines.push({ k: "del", o: oldNo++, n: null, t: body });
11642
+ f.removed++;
11643
+ } else if (tag === " ") {
11644
+ hunk.lines.push({ k: "ctx", o: oldNo++, n: newNo++, t: body });
11645
+ }
11646
+ }
11647
+ push();
11648
+ return files;
11649
+ }
11650
+ function truncateFile(f) {
11651
+ let count = 0;
11652
+ const hunks = [];
11653
+ let cut = false;
11654
+ for (const h of f.hunks) {
11655
+ if (count >= MAX_LINES_PER_FILE) {
11656
+ cut = true;
11657
+ break;
11658
+ }
11659
+ const room = MAX_LINES_PER_FILE - count;
11660
+ if (h.lines.length <= room) {
11661
+ hunks.push(h);
11662
+ count += h.lines.length;
11663
+ } else {
11664
+ hunks.push({ header: h.header, lines: h.lines.slice(0, room) });
11665
+ count += room;
11666
+ cut = true;
11667
+ break;
11668
+ }
11669
+ }
11670
+ return cut ? { ...f, hunks, truncated: true } : f;
11671
+ }
11672
+ async function resolveBase(wt, baseBranch) {
11673
+ const tryRef = async (ref) => {
11674
+ const sha = (await $4({ reject: false })`git -C ${wt} rev-parse --verify --quiet ${ref + "^{commit}"}`).stdout.trim();
11675
+ if (!sha) return null;
11676
+ const mb2 = (await $4({ reject: false })`git -C ${wt} merge-base HEAD ${ref}`).stdout.trim();
11677
+ return mb2 || null;
11678
+ };
11679
+ const originHead = (await $4({ reject: false })`git -C ${wt} symbolic-ref --quiet --short refs/remotes/origin/HEAD`).stdout.trim();
11680
+ const candidates = [
11681
+ ...baseBranch ? [`origin/${baseBranch}`, baseBranch] : [],
11682
+ originHead,
11683
+ "origin/main",
11684
+ "origin/master",
11685
+ "main",
11686
+ "master"
11687
+ ].filter(Boolean);
11688
+ for (const c of candidates) {
11689
+ const mb2 = await tryRef(c);
11690
+ if (mb2) return mb2;
11691
+ }
11692
+ return null;
11693
+ }
11694
+ function mtime(path) {
11695
+ try {
11696
+ return statSync10(path).mtimeMs;
11697
+ } catch {
11698
+ return 0;
11699
+ }
11700
+ }
11701
+ async function collectRunDiff(worktree, opts = {}) {
11702
+ const stub = (reason) => ({ ok: false, reason, pr: opts.pr ?? null, files: [], added: 0, removed: 0 });
11703
+ 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";
11704
+ if (!worktree) return stub(gone);
11705
+ const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
11706
+ if (isWt !== "true") return stub(gone);
11707
+ const base3 = await resolveBase(worktree, opts.baseBranch);
11708
+ const from = base3 ?? "HEAD";
11709
+ const trackedTxt = (await $4({ reject: false })`git -C ${worktree} diff --no-color --find-renames ${from} --`).stdout;
11710
+ let files = parseUnifiedDiff(trackedTxt);
11711
+ const untrackedRaw = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
11712
+ const untracked = untrackedRaw.split("\0").filter(Boolean).slice(0, MAX_UNTRACKED);
11713
+ for (const rel of untracked) {
11714
+ const t = (await $4({ reject: false })`git -C ${worktree} diff --no-color --no-index -- /dev/null ${rel}`).stdout;
11715
+ for (const pf of parseUnifiedDiff(t)) {
11716
+ pf.status = "added";
11717
+ if (!pf.path || pf.path === "/dev/null") pf.path = rel;
11718
+ files.push(pf);
11719
+ }
11720
+ }
11721
+ files.sort((a, b) => mtime(join25(worktree, b.path)) - mtime(join25(worktree, a.path)));
11722
+ const truncatedFiles = files.length > MAX_FILES;
11723
+ if (truncatedFiles) files = files.slice(0, MAX_FILES);
11724
+ const added = files.reduce((s, f) => s + f.added, 0);
11725
+ const removed = files.reduce((s, f) => s + f.removed, 0);
11726
+ const out = files.map(truncateFile);
11727
+ return {
11728
+ ok: true,
11729
+ base: base3 ? base3.slice(0, 9) : null,
11730
+ files: out,
11731
+ added,
11732
+ removed,
11733
+ ...truncatedFiles ? { truncated: true } : {}
11734
+ };
11735
+ }
11736
+ async function changedPaths(worktree, opts = {}) {
11737
+ if (!worktree) return [];
11738
+ try {
11739
+ const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
11740
+ if (isWt !== "true") return [];
11741
+ const from = await resolveBase(worktree, opts.baseBranch) ?? "HEAD";
11742
+ const tracked = (await $4({ reject: false })`git -C ${worktree} diff --name-only --find-renames ${from} --`).stdout;
11743
+ const untracked = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
11744
+ const all = [...tracked.split("\n"), ...untracked.split("\0")].map((s) => s.trim()).filter(Boolean);
11745
+ return [...new Set(all)].sort();
11746
+ } catch {
11747
+ return [];
11748
+ }
11749
+ }
11750
+
11475
11751
  // src/zoneNotes.ts
11476
11752
  init_src();
11477
11753
  function parseZoneMemory(raw, zone) {
@@ -11520,6 +11796,7 @@ function collectZoneNoteConfirmations(report) {
11520
11796
 
11521
11797
  // src/droneServer.ts
11522
11798
  init_src();
11799
+ init_src();
11523
11800
 
11524
11801
  // src/reviewCycle.ts
11525
11802
  init_src();
@@ -11769,19 +12046,36 @@ ${failText}` : failText }],
11769
12046
  );
11770
12047
  server.tool(
11771
12048
  "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.",
12049
+ "\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
12050
  {
12051
+ // TASWM-505: КОД причины рядом с прозой. Проза объясняет автору ЭТУ задачу, код
12052
+ // складывается по бэклогу в распределение «чего в постановках не хватает чаще
12053
+ // всего» (аналитика роя). Обязательный: необязательное поле дрон пропустит, а
12054
+ // распределение по половине возвратов — не распределение.
12055
+ reason: z.enum(RETURN_REASONS).describe(
12056
+ "\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"
12057
+ ),
11774
12058
  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
12059
  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
12060
  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")
12061
+ 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"),
12062
+ // TASWM-505: разбиение — ПРЕДЛОЖЕНИЕ автору, а не действие дрона. Подзадачи дрон не
12063
+ // заводит сознательно: разбиение — продуктовое решение, а плохое разбиение хуже
12064
+ // никакого, потому что выглядит авторитетно (заведённые подзадачи прочтут как
12065
+ // согласованный план). Поэтому — строчками в комменте, которые автор правит или
12066
+ // выбрасывает одним движением.
12067
+ split: z.array(z.string().min(10)).max(7).optional().describe(
12068
+ "\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)"
12069
+ )
11778
12070
  },
11779
12071
  guard(
11780
12072
  async ({
12073
+ reason,
11781
12074
  missing,
11782
12075
  readings,
11783
12076
  assumed,
11784
- questions
12077
+ questions,
12078
+ split
11785
12079
  }) => {
11786
12080
  if (await overmindDead()) return freeze();
11787
12081
  const run = await store.myRun();
@@ -11816,10 +12110,20 @@ ${failText}` : failText }],
11816
12110
  "",
11817
12111
  `${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
12112
  "",
12113
+ `**\u041F\u0440\u0438\u0447\u0438\u043D\u0430.** ${returnReasonLabel(reason)} (\`${reason}\`).`,
12114
+ "",
11819
12115
  `**\u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442.** ${missing.trim()}`,
11820
12116
  ...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
12117
  "",
11822
12118
  `**\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()}`,
12119
+ // Разбиение — только предложение и только к too_big (TASWM-505). Формулировка
12120
+ // «на твоё усмотрение» здесь не вежливость: заведённые подзадачи выглядели бы
12121
+ // согласованным планом, а это решение автора, и плохое разбиение хуже никакого.
12122
+ ...reason === "too_big" && split?.length ? [
12123
+ "",
12124
+ "**\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.",
12125
+ ...split.map((s) => `- ${s.trim()}`)
12126
+ ] : [],
11823
12127
  "",
11824
12128
  "**\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
12129
  ...questions.map((q, i) => `${i + 1}. ${q.trim()}`)
@@ -11863,9 +12167,9 @@ ${failText}` : failText }],
11863
12167
  };
11864
12168
  }
11865
12169
  }
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);
12170
+ 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()}`;
12171
+ await store.updateRun(null, { status: "returned", stop_reason: stopReason, return_reason: reason });
12172
+ await store.addEvent(null, "returned", failures.length ? `${stopReason} \xB7 \u043D\u0435 \u0434\u043E\u0435\u0445\u0430\u043B\u043E: ${failures.join("; ")}` : stopReason);
11869
12173
  return {
11870
12174
  content: [
11871
12175
  {
@@ -12211,9 +12515,11 @@ ${report}${shots.warning}`,
12211
12515
  const headBranch = opts.prHeadBranch ? await opts.prHeadBranch(pr_url).catch(() => null) : null;
12212
12516
  const branchFix = branchFromPrPatch(run?.branch, headBranch);
12213
12517
  if (branchFix) await store.addEvent(null, "step", branchFix.event);
12518
+ const held = await changedPaths(opts.worktree ?? process.cwd(), { baseBranch: run?.base_branch ?? null }).catch(() => []);
12519
+ const heldPatch = held.length ? { changed_paths: held } : {};
12214
12520
  if (run) {
12215
12521
  const t = reviewTransitionPatch(run, pr_url);
12216
- await store.updateRun(null, { ...t.patch, ...branchFix?.patch ?? {} });
12522
+ await store.updateRun(null, { ...t.patch, ...branchFix?.patch ?? {}, ...heldPatch });
12217
12523
  await store.addEvent(null, "pr_opened", pr_url);
12218
12524
  await postReport(reportSlug(run.task_slug), t.cycle ?? run.review_cycle ?? 0);
12219
12525
  const rulesNote = await offerRules(reportSlug(run.task_slug));
@@ -12228,7 +12534,7 @@ ${report}${shots.warning}`,
12228
12534
  }
12229
12535
  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
12536
  }
12231
- await store.updateRun(null, { status: "review", pr_url, ...branchFix?.patch ?? {} });
12537
+ await store.updateRun(null, { status: "review", pr_url, ...branchFix?.patch ?? {}, ...heldPatch });
12232
12538
  await store.addEvent(null, "pr_opened", pr_url);
12233
12539
  const fallbackSlug = reportSlug(void 0);
12234
12540
  if (fallbackSlug) await postReport(fallbackSlug, void 0);
@@ -13532,20 +13838,20 @@ function makeMergeGateParts(deps) {
13532
13838
 
13533
13839
  // src/envGate.ts
13534
13840
  import { existsSync as existsSync15 } from "fs";
13535
- import { join as join26 } from "path";
13841
+ import { join as join27 } from "path";
13536
13842
  import { createConnection } from "net";
13537
13843
  import { execa as execa2 } from "execa";
13538
13844
 
13539
13845
  // src/platform.ts
13540
13846
  import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
13541
- import { delimiter, join as join25 } from "path";
13847
+ import { delimiter, join as join26 } from "path";
13542
13848
  function resolveCommandPath(name, env = process.env, platform = process.platform) {
13543
13849
  if (name.includes("/") || name.includes("\\")) return existsSync14(name) ? name : null;
13544
13850
  const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
13545
13851
  const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
13546
13852
  for (const dir of dirs) {
13547
13853
  for (const ext of exts) {
13548
- const candidate = join25(dir, name + ext);
13854
+ const candidate = join26(dir, name + ext);
13549
13855
  if (existsSync14(candidate)) return candidate;
13550
13856
  }
13551
13857
  }
@@ -13630,7 +13936,7 @@ var COMPOSE_MARKERS = [
13630
13936
  ];
13631
13937
  function detectRequirements(repo, io = {}) {
13632
13938
  const exists = io.exists ?? existsSync15;
13633
- const marker = COMPOSE_MARKERS.find((f) => exists(join26(repo, f)));
13939
+ const marker = COMPOSE_MARKERS.find((f) => exists(join27(repo, f)));
13634
13940
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
13635
13941
  }
13636
13942
  function resolveRequirements(repo, declared, io = {}) {
@@ -13765,210 +14071,10 @@ import { randomBytes as randomBytes3 } from "crypto";
13765
14071
  import { basename as basename5 } from "path";
13766
14072
  import { serve as serve2 } from "@hono/node-server";
13767
14073
 
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
14074
  // package.json
13969
14075
  var package_default = {
13970
14076
  name: "@taskless-app/swarm",
13971
- version: "0.6.25",
14077
+ version: "0.6.26",
13972
14078
  type: "module",
13973
14079
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
13974
14080
  keywords: [
@@ -14019,7 +14125,7 @@ var package_default = {
14019
14125
  execa: "^10.0.0",
14020
14126
  zod: "3.25.76"
14021
14127
  },
14022
- gitHead: "36aaed0414a46ea272ad762b81e051fecec40eaa"
14128
+ gitHead: "52416995d46b0a843374d14bfb00dd310f71c4bf"
14023
14129
  };
14024
14130
 
14025
14131
  // src/version.ts
@@ -16401,6 +16507,12 @@ function predictForRun(model, run) {
16401
16507
  function short(files, n = 3) {
16402
16508
  return files.slice(0, n).join(", ") + (files.length > n ? ` \u0438 \u0435\u0449\u0451 ${files.length - n}` : "");
16403
16509
  }
16510
+ function heldBecause(w) {
16511
+ const parts = [];
16512
+ 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}`);
16513
+ 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");
16514
+ return parts.length ? ` (${parts.join("; ")})` : "";
16515
+ }
16404
16516
  var SPINE_PREDICTION_WEIGHT = 0.7;
16405
16517
  function spineOfPrediction(spine, predicted) {
16406
16518
  return predicted.filter((p) => spine.has(p.path) && p.weight >= SPINE_PREDICTION_WEIGHT).map((p) => p.path);
@@ -16421,7 +16533,7 @@ function decideConflict({ model, candidate, active }) {
16421
16533
  return {
16422
16534
  kind: "wait",
16423
16535
  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)")
16536
+ 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
16537
  };
16426
16538
  }
16427
16539
  }
@@ -16432,7 +16544,7 @@ function decideConflict({ model, candidate, active }) {
16432
16544
  return {
16433
16545
  kind: "wait",
16434
16546
  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)")
16547
+ 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
16548
  };
16437
16549
  }
16438
16550
  return { kind: "clear" };
@@ -16485,10 +16597,16 @@ var ActiveFilesCache = class {
16485
16597
  this.cache.set(run.id, { at: now, files });
16486
16598
  return files;
16487
16599
  }
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);
16600
+ /**
16601
+ * Забыть всех, кто больше НЕ ДЕРЖИТ файлы. Раньше здесь стоял список ЖИВЫХ дронов и
16602
+ * комментарий «дрон закончил — его файлы больше не заняты»; это и была дырка TASWM-506:
16603
+ * дрон заканчивал, открывал PR, PR висел непомерженным — а строки уже считались
16604
+ * свободными. Держатель теперь переживает своего дрона и уходит из кэша на мерже
16605
+ * (прогон выходит из `review`), см. holdsFiles.
16606
+ */
16607
+ prune(holderIds) {
16608
+ const holders = new Set(holderIds);
16609
+ for (const id of [...this.cache.keys()]) if (!holders.has(id)) this.cache.delete(id);
16492
16610
  }
16493
16611
  };
16494
16612
  function withManualAreas(history, areas) {
@@ -17097,12 +17215,19 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
17097
17215
  // вторую ветку мимо PR. Пишем ту, что реально записана на прогоне; пусто (первый спавн —
17098
17216
  // ветку заводит spawnDrone из слага) — прежняя формулировка.
17099
17217
  `\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).",
17218
+ "\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
17219
  // TASWM-412: третий выход из непонятной задачи. Раньше их было два, и оба плохие —
17102
17220
  // угадать постановку (сделает не то) или ask (слот занят, а ответить может только автор
17103
17221
  // задачи, не оператор роя). Граница здесь ОБЯЗАТЕЛЬНА: дрон, легко возвращающий задачи,
17104
17222
  // становится отписочником, а это хуже, чем дрон, который иногда угадывает неверно.
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.",
17223
+ "\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.",
17224
+ // TASWM-505: ЕДИНСТВЕННЫЙ сорт негодной задачи, который не покрывался ничем. claimPolicy
17225
+ // (TASWM-357) отсеивает родителей с незакрытыми детьми, но лист на 8 часов уезжает дрону
17226
+ // целиком — и он упирается в потолок трат на середине, оставляя недоделанную работу
17227
+ // человеку. Это уже случалось. Подзадачи дрон при этом НЕ заводит: разбиение —
17228
+ // продуктовое решение, а плохое разбиение хуже никакого, потому что выглядит
17229
+ // авторитетно (заведённые подзадачи читаются как согласованный план).
17230
+ "\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
17231
  "\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
17232
  // TASWM-377: у сессии есть ПОТОЛОК ТРАТ, и он режет её без предупреждения, прямо посреди
17108
17233
  // работы. Дрон об этом знать обязан: цена сессии растёт от ЧИСЛА ХОДОВ (каждый ход заново
@@ -17915,15 +18040,17 @@ function effectiveRepo(deps, run) {
17915
18040
  async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback) {
17916
18041
  const model = await models.forRepo(repo);
17917
18042
  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));
18043
+ const holders = db.listRuns().filter((r) => holdsFiles(r));
18044
+ activeFiles.prune(holders.map((r) => r.id));
17920
18045
  const active = [];
17921
- for (const r of live) {
18046
+ for (const r of holders) {
17922
18047
  if (r.id === run.id) continue;
17923
18048
  if (normalizeRepo(r.repo || repoFallback || "") !== repo) continue;
17924
- const files = await activeFiles.filesOf(r);
18049
+ const snapshot = r.changed_paths ?? [];
18050
+ const files = snapshot.length ? snapshot : await activeFiles.filesOf(r);
18051
+ const prUrl = r.status === "review" ? r.pr_url : null;
17925
18052
  active.push(
17926
- files.length ? { run: r, files, actual: true } : { run: r, files: predictForRun(model, r).map((p) => p.path), actual: false }
18053
+ files.length ? { run: r, files, actual: true, prUrl } : { run: r, files: predictForRun(model, r).map((p) => p.path), actual: false, prUrl }
17927
18054
  );
17928
18055
  }
17929
18056
  return decideConflict({ model, candidate: run, active });
@@ -18115,6 +18242,13 @@ async function trySpawnQueuedRun(deps, run) {
18115
18242
  if (logged.get(run.id) !== e.message) {
18116
18243
  logged.set(run.id, e.message);
18117
18244
  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)`);
18245
+ const blocker = deps.db.getRun(e.blockedByRunId);
18246
+ if (blocker)
18247
+ deps.db.addEvent(
18248
+ blocker.id,
18249
+ "step",
18250
+ `${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)`
18251
+ );
18118
18252
  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
18253
  }
18120
18254
  return "skipped";
@@ -21099,5 +21233,6 @@ export {
21099
21233
  tickOnce,
21100
21234
  verdictCommentMark,
21101
21235
  wakeRun,
21102
- worktreeFromCmd
21236
+ worktreeFromCmd,
21237
+ zoneConflictFor
21103
21238
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.6.25",
3
+ "version": "0.6.26",
4
4
  "type": "module",
5
5
  "description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "execa": "^10.0.0",
40
40
  "zod": "3.25.76"
41
41
  },
42
- "gitHead": "36aaed0414a46ea272ad762b81e051fecec40eaa",
42
+ "gitHead": "52416995d46b0a843374d14bfb00dd310f71c4bf",
43
43
  "scripts": {
44
44
  "overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
45
45
  "swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",