@taskless-app/swarm 0.6.14 → 0.6.16

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
@@ -93,6 +93,13 @@ var init_types = __esm({
93
93
  // вопрос дрона оператору (прод: SwarmEvent kind=question, TASWM-74)
94
94
  "answer",
95
95
  // ответ оператора на вопрос дрона (прод: SwarmEvent kind=answer)
96
+ // Черновик ответа, собранный машиной (TASWM-430): контекст задачи/репозитория + один
97
+ // вызов модели, реплика в ленте прогона рядом с вопросом. НЕ "answer" с флагом — и это
98
+ // главное в этом kind: вопрос закрывают (и дрона будят) все, кто сравнивает kind со
99
+ // строкой "answer"/"answered" — прод (state/taskRuns/timeline/fleetMetrics), веб-чат и
100
+ // синхронизация демона (droneBridge тянет вниз только ответ). Отдельный kind не знает
101
+ // никто из них, поэтому черновик физически не может разбудить дрона: он ждёт человека.
102
+ "answer_draft",
96
103
  "ci_wake",
97
104
  // review-PR покраснел (gh pr checks) — рой разбудил дрона со сжатым summary фейла (TASWM-102)
98
105
  "ci_checked",
@@ -332,9 +339,22 @@ var init_timeline = __esm({
332
339
  });
333
340
 
334
341
  // ../swarm-core/src/reviewPhase.ts
342
+ var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
335
343
  var init_reviewPhase = __esm({
336
344
  "../swarm-core/src/reviewPhase.ts"() {
337
345
  "use strict";
346
+ REVIEW_PHASE_TEXT = {
347
+ checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
348
+ awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
349
+ ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
350
+ in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
351
+ changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
352
+ ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
353
+ 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"
354
+ };
355
+ PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
356
+ (a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
357
+ );
338
358
  }
339
359
  });
340
360
 
@@ -534,7 +554,7 @@ var init_status = __esm({
534
554
  // ../swarm-core/src/deps.ts
535
555
  function judgeDep(runs) {
536
556
  const real = runs.filter((r) => !r.service);
537
- if (real.length === 0) return { ...PENDING };
557
+ if (real.length === 0) return { ...UNKNOWN };
538
558
  if (real.some((r) => !isTerminalStatus(r.status))) return { ...PENDING };
539
559
  if (real.some((r) => r.status === "done")) {
540
560
  return { outcome: "done", met: true, status: "done", name: null, stopReason: null };
@@ -548,12 +568,13 @@ function judgeDep(runs) {
548
568
  stopReason: last.stopReason ?? null
549
569
  };
550
570
  }
551
- var PENDING;
571
+ var PENDING, UNKNOWN;
552
572
  var init_deps = __esm({
553
573
  "../swarm-core/src/deps.ts"() {
554
574
  "use strict";
555
575
  init_status();
556
576
  PENDING = { outcome: "pending", met: false, status: null, name: null, stopReason: null };
577
+ UNKNOWN = { outcome: "unknown", met: false, status: null, name: null, stopReason: null };
557
578
  }
558
579
  });
559
580
 
@@ -744,6 +765,28 @@ function splitQueue(items, ref) {
744
765
  }
745
766
  return { queue, hosted };
746
767
  }
768
+ function hostTickStallMs(intervalMs) {
769
+ const iv = intervalMs != null && Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : 0;
770
+ return Math.max(iv * HOST_TICK_STALL_TICKS, HOST_TICK_STALL_FLOOR_MS);
771
+ }
772
+ function hostTickAgeMs(host, nowMs) {
773
+ const tick = host.tick;
774
+ if (!tick || !tick.at) return null;
775
+ const at = Date.parse(tick.at);
776
+ if (Number.isNaN(at)) return null;
777
+ return Math.max(0, nowMs - at);
778
+ }
779
+ function hostTickStalled(host, nowMs, staleMs2 = HOST_HEARTBEAT_STALE_MS) {
780
+ if (host.lastSeenAt !== void 0 && isHostOffline(host.lastSeenAt, nowMs, staleMs2)) return false;
781
+ const age = hostTickAgeMs(host, nowMs);
782
+ if (age == null) return false;
783
+ return age > hostTickStallMs(host.tick?.intervalMs);
784
+ }
785
+ function hostTickStalledText(ageMs) {
786
+ const min = ageMs == null ? null : Math.max(1, Math.round(ageMs / 6e4));
787
+ const how = min == null ? "\u0442\u0438\u043A \u043D\u0435 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044F" : `\u0442\u0438\u043A \u043D\u0435 \u0434\u0432\u0438\u0433\u0430\u043B\u0441\u044F ${min} \u043C\u0438\u043D`;
788
+ return `\u0446\u0438\u043A\u043B \u0432\u0441\u0442\u0430\u043B \u2014 \u043F\u0443\u043B\u044C\u0441 \u0438\u0434\u0451\u0442, ${how}; ${HOST_TICK_STALLED_ACTION}`;
789
+ }
747
790
  function groupByHost(items, ref, opts = {}) {
748
791
  const { hosts = [], nowMs = Date.now(), staleMs: staleMs2 = HOST_HEARTBEAT_STALE_MS } = opts;
749
792
  const infoById = new Map(hosts.map((h) => [h.hostId, h]));
@@ -797,6 +840,9 @@ function hostStateText(host, nowMs = Date.now(), staleMs2 = HOST_HEARTBEAT_STALE
797
840
  const at = Date.parse(host.lastSeenAt);
798
841
  if (Number.isNaN(at)) return "\u043F\u0443\u043B\u044C\u0441\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E";
799
842
  if (!isHostOffline(host.lastSeenAt, nowMs, staleMs2)) {
843
+ if (hostTickStalled({ tick: host.tick, lastSeenAt: host.lastSeenAt }, nowMs, staleMs2)) {
844
+ return hostTickStalledText(hostTickAgeMs({ tick: host.tick }, nowMs));
845
+ }
800
846
  if (hostNotSignedIn(host)) return hostSignInHint(host.webUrl);
801
847
  if (host.paused) return host.pauseReason ? `\u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 ${host.pauseReason}` : "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435";
802
848
  if (host.storm) return host.stormReason ? `\u0448\u0442\u043E\u0440\u043C\u0438\u0442 \u2014 ${host.stormReason}` : "\u0448\u0442\u043E\u0440\u043C\u0438\u0442 \u2014 \u043D\u043E\u0432\u044B\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442";
@@ -808,7 +854,7 @@ function hostStateText(host, nowMs = Date.now(), staleMs2 = HOST_HEARTBEAT_STALE
808
854
  function shouldGroupByHost(groups) {
809
855
  return groups.length > 1;
810
856
  }
811
- var ANY_HOST_TOKENS, UNKNOWN_HOST_KEY, UNKNOWN_HOST_LABEL, QUEUE_LABEL, NOT_STARTED_STATUSES, HOST_HEARTBEAT_STALE_MS, HOST_ACCESS_REVOKED_TEXT;
857
+ var ANY_HOST_TOKENS, UNKNOWN_HOST_KEY, UNKNOWN_HOST_LABEL, QUEUE_LABEL, NOT_STARTED_STATUSES, HOST_HEARTBEAT_STALE_MS, HOST_TICK_STALL_TICKS, HOST_TICK_STALL_FLOOR_MS, HOST_TICK_STALLED_ACTION, HOST_ACCESS_REVOKED_TEXT;
812
858
  var init_hosts = __esm({
813
859
  "../swarm-core/src/hosts.ts"() {
814
860
  "use strict";
@@ -818,6 +864,9 @@ var init_hosts = __esm({
818
864
  QUEUE_LABEL = "\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438";
819
865
  NOT_STARTED_STATUSES = /* @__PURE__ */ new Set(["queued"]);
820
866
  HOST_HEARTBEAT_STALE_MS = 45e3;
867
+ HOST_TICK_STALL_TICKS = 10;
868
+ HOST_TICK_STALL_FLOOR_MS = 12e4;
869
+ HOST_TICK_STALLED_ACTION = '\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0434\u0435\u043C\u043E\u043D \u043C\u0430\u0448\u0438\u043D\u044B \u2014 swarm_host_command{kind:"restart"}';
821
870
  HOST_ACCESS_REVOKED_TEXT = "\u0434\u043E\u0441\u0442\u0443\u043F \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u2014 \u0432\u043E\u0439\u0434\u0438 \u0437\u0430\u043D\u043E\u0432\u043E \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 (swarm login)";
822
871
  }
823
872
  });
@@ -869,10 +918,27 @@ var init_hostResources = __esm({
869
918
  });
870
919
 
871
920
  // ../swarm-core/src/hostPool.ts
921
+ var HOST_REJECTION_TEXT, HOST_REPO_BLOCK_TEXT;
872
922
  var init_hostPool = __esm({
873
923
  "../swarm-core/src/hostPool.ts"() {
874
924
  "use strict";
875
925
  init_hosts();
926
+ HOST_REJECTION_TEXT = {
927
+ offline: "\u043D\u0435 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438",
928
+ tick_stalled: `\u0446\u0438\u043A\u043B \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B\u0430 \u0432\u0441\u0442\u0430\u043B \u2014 \u043F\u0443\u043B\u044C\u0441 \u0438\u0434\u0451\u0442, \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442; ${HOST_TICK_STALLED_ACTION}`,
929
+ not_signed_in: "\u043D\u0435 \u0432\u043E\u0448\u043B\u0430 \u0432 Taskless \u2014 \u043D\u0430 \u0435\u0451 \u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0435 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D \u0432\u0445\u043E\u0434",
930
+ paused: "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435",
931
+ draining: "\u0432\u044B\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435",
932
+ repos_off: "\u0432\u0441\u0435 \u0435\u0451 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u044B",
933
+ full: "\u0437\u0430\u043D\u044F\u0442\u0430 \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E"
934
+ };
935
+ HOST_REPO_BLOCK_TEXT = {
936
+ offline: HOST_REJECTION_TEXT.offline,
937
+ paused: HOST_REJECTION_TEXT.paused,
938
+ draining: HOST_REJECTION_TEXT.draining,
939
+ no_clone: "\u043A\u043B\u043E\u043D\u0430 \u043D\u0435\u0442",
940
+ repo_off: "\u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u0430"
941
+ };
876
942
  }
877
943
  });
878
944
 
@@ -1501,13 +1567,50 @@ var init_claudeBin = __esm({
1501
1567
  });
1502
1568
 
1503
1569
  // src/swarmFiles.ts
1504
- var SWARM_MCP_CONFIG_FILE, SWARM_TASK_DIR, SWARM_EXCLUDE_PATTERNS;
1570
+ function sq(s) {
1571
+ return `'${s.replace(/'/g, `'\\''`)}'`;
1572
+ }
1573
+ function guardHookScript(hook, delegate) {
1574
+ const paths = SWARM_WORKTREE_ARTIFACTS.map(sq).join(" ");
1575
+ const lines = [
1576
+ "#!/usr/bin/env sh",
1577
+ `# \u0421\u0442\u0440\u0430\u0445\u043E\u0432\u043A\u0430 \u0440\u043E\u044F (TASWM-372): \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u0440\u043E\u044F \u043D\u0435 \u0443\u0435\u0437\u0436\u0430\u044E\u0442 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.`,
1578
+ "# \u0425\u0443\u043A \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D \u041F\u0415\u0420-WORKTREE (git config --worktree core.hooksPath) \u043F\u0440\u0438 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0435",
1579
+ "# worktree \u0434\u0440\u043E\u043D\u0430; \u0445\u0443\u043A\u0438 \u0441\u0430\u043C\u043E\u0433\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u043D\u0435 \u0442\u0440\u043E\u043D\u0443\u0442\u044B \u2014 \u043F\u0440\u0435\u0436\u043D\u0438\u0439 \u0432\u044B\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043D\u0438\u0436\u0435.",
1580
+ `swarm_found=$(git ls-files -- ${paths} 2>/dev/null)`,
1581
+ `if [ -n "$swarm_found" ]; then`,
1582
+ ` echo "\u2716 \u0421\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u0420\u041E\u042F \u0432 \u0438\u043D\u0434\u0435\u043A\u0441\u0435 git \u2014 ${ACTION[hook]} (TASWM-372):" >&2`,
1583
+ ` printf '%s\\n' "$swarm_found" | sed 's/^/ /' >&2`,
1584
+ ` echo " \u042D\u0442\u043E \u0444\u0430\u0439\u043B\u044B \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0440\u043E\u044F, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0432 ${SWARM_MCP_CONFIG_FILE} \u043B\u0435\u0436\u0438\u0442 \u0442\u043E\u043A\u0435\u043D," >&2`,
1585
+ ` echo " \u043A\u043E\u0442\u043E\u0440\u043E\u043C\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u043E\u043A\u0430\u0437\u0430\u0442\u044C\u0441\u044F \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 (\u0438\u043D\u0446\u0438\u0434\u0435\u043D\u0442 11.08)." >&2`,
1586
+ ` echo " \u0423\u0431\u0435\u0440\u0438 \u0438\u0445 \u0438\u0437 \u0438\u043D\u0434\u0435\u043A\u0441\u0430 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438:" >&2`,
1587
+ ` echo " git rm --cached -r --ignore-unmatch ${SWARM_WORKTREE_ARTIFACTS.join(" ")}" >&2`,
1588
+ ` echo " \u0424\u0430\u0439\u043B\u044B \u043D\u0430 \u0434\u0438\u0441\u043A\u0435 \u043E\u0441\u0442\u0430\u043D\u0443\u0442\u0441\u044F \u2014 \u043E\u043D\u0438 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u043E\u0433\u043E\u043D\u0443." >&2`,
1589
+ " exit 1",
1590
+ "fi"
1591
+ ];
1592
+ if (delegate) {
1593
+ lines.push(
1594
+ "# \u041F\u0440\u0435\u0436\u043D\u0438\u0439 \u0445\u0443\u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F (husky \u0438 \u0442.\u043F.) \u2014 \u043E\u043D \u043D\u0435 \u0434\u043E\u043B\u0436\u0435\u043D \u043F\u0440\u043E\u043F\u0430\u0441\u0442\u044C \u0438\u0437-\u0437\u0430 \u043D\u0430\u0448\u0435\u0439 \u0441\u0442\u0440\u0430\u0445\u043E\u0432\u043A\u0438.",
1595
+ `if [ -x ${sq(delegate)} ]; then exec ${sq(delegate)} "$@"; fi`
1596
+ );
1597
+ }
1598
+ lines.push("exit 0", "");
1599
+ return lines.join("\n");
1600
+ }
1601
+ var SWARM_MCP_CONFIG_FILE, SWARM_TASK_DIR, SWARM_WORKTREE_ARTIFACTS, SWARM_EXCLUDE_PATTERNS, GUARD_HOOKS, ACTION;
1505
1602
  var init_swarmFiles = __esm({
1506
1603
  "src/swarmFiles.ts"() {
1507
1604
  "use strict";
1508
1605
  SWARM_MCP_CONFIG_FILE = ".swarm-mcp.json";
1509
1606
  SWARM_TASK_DIR = ".swarm-task";
1607
+ SWARM_WORKTREE_ARTIFACTS = [SWARM_MCP_CONFIG_FILE, SWARM_TASK_DIR];
1510
1608
  SWARM_EXCLUDE_PATTERNS = [`/${SWARM_MCP_CONFIG_FILE}`, `/${SWARM_TASK_DIR}/`];
1609
+ GUARD_HOOKS = ["pre-commit", "pre-push"];
1610
+ ACTION = {
1611
+ "pre-commit": "\u043A\u043E\u043C\u043C\u0438\u0442 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D",
1612
+ "pre-push": "\u043F\u0443\u0448 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D"
1613
+ };
1511
1614
  }
1512
1615
  });
1513
1616
 
@@ -1939,7 +2042,10 @@ function normalizeRepo(repo) {
1939
2042
  }
1940
2043
  }
1941
2044
  function droneWorktreePath(worktreesDir, repo, slug) {
1942
- return join4(worktreesDir, basename(normalizeRepo(repo)), slug);
2045
+ return join4(droneWorktreeRoot(worktreesDir, repo), slug);
2046
+ }
2047
+ function droneWorktreeRoot(worktreesDir, repo) {
2048
+ return join4(worktreesDir, basename(normalizeRepo(repo)));
1943
2049
  }
1944
2050
  function resolveEffectiveWorktreesDir(deps) {
1945
2051
  if (deps.envOverride) return deps.envOverride;
@@ -1965,7 +2071,7 @@ function tokenFromFile() {
1965
2071
  return "";
1966
2072
  }
1967
2073
  }
1968
- var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
2074
+ var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
1969
2075
  var init_config = __esm({
1970
2076
  "src/config.ts"() {
1971
2077
  "use strict";
@@ -2027,6 +2133,7 @@ var init_config = __esm({
2027
2133
  SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
2028
2134
  REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join4(SWARM_HOME, "repo-map.json");
2029
2135
  SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
2136
+ SWARM_REPO_SCAN_TTL_MS = envNum("SWARM_REPO_SCAN_TTL_MS", 24 * 60 * 6e4);
2030
2137
  TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
2031
2138
  SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
2032
2139
  TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
@@ -2053,6 +2160,7 @@ var init_config = __esm({
2053
2160
  SWARM_ANOMALY_QUESTION_UNANSWERED_MS = envNum("SWARM_ANOMALY_QUESTION_UNANSWERED_MS", 20 * 6e4);
2054
2161
  SWARM_ANOMALY_OUTBOX_STUCK_MS = envNum("SWARM_ANOMALY_OUTBOX_STUCK_MS", 10 * 6e4);
2055
2162
  SWARM_ANOMALY_RESOURCE_GATE_STALL_MS = envNum("SWARM_ANOMALY_RESOURCE_GATE_STALL_MS", 5 * 6e4);
2163
+ SWARM_ANOMALY_DEP_MISSING_MS = envNum("SWARM_ANOMALY_DEP_MISSING_MS", 2 * 6e4);
2056
2164
  SWARM_WATCHDOG_STALL_MS = envNum("SWARM_WATCHDOG_STALL_MS", 15 * 6e4);
2057
2165
  }
2058
2166
  });
@@ -2167,7 +2275,7 @@ var init_package = __esm({
2167
2275
  "package.json"() {
2168
2276
  package_default = {
2169
2277
  name: "@taskless-app/swarm",
2170
- version: "0.6.14",
2278
+ version: "0.6.16",
2171
2279
  type: "module",
2172
2280
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
2173
2281
  keywords: [
@@ -2217,7 +2325,7 @@ var init_package = __esm({
2217
2325
  execa: "^10.0.0",
2218
2326
  zod: "3.25.76"
2219
2327
  },
2220
- gitHead: "a3b11498dbed5db3642fe4fc83bcd2054efde055"
2328
+ gitHead: "785b1478f2b81406c9b6524913939707b6c096fb"
2221
2329
  };
2222
2330
  }
2223
2331
  });
@@ -2235,7 +2343,7 @@ var init_version = __esm({
2235
2343
 
2236
2344
  // src/droneChannel.ts
2237
2345
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
2238
- import { chmodSync, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
2346
+ import { chmodSync, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
2239
2347
  import { dirname as dirname2 } from "path";
2240
2348
  import { serve } from "@hono/node-server";
2241
2349
  function loadChannelSecret(path) {
@@ -2245,7 +2353,7 @@ function loadChannelSecret(path) {
2245
2353
  } catch {
2246
2354
  }
2247
2355
  const secret = randomBytes(32).toString("hex");
2248
- mkdirSync5(dirname2(path), { recursive: true });
2356
+ mkdirSync6(dirname2(path), { recursive: true });
2249
2357
  writeFileSync5(path, secret, { mode: 384 });
2250
2358
  try {
2251
2359
  chmodSync(path, 384);
@@ -2287,7 +2395,7 @@ var init_droneChannel = __esm({
2287
2395
 
2288
2396
  // src/agentToken.ts
2289
2397
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
2290
- import { join as join8 } from "path";
2398
+ import { join as join9 } from "path";
2291
2399
  function loadAgentToken() {
2292
2400
  try {
2293
2401
  const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
@@ -2368,7 +2476,7 @@ var init_agentToken = __esm({
2368
2476
  "src/agentToken.ts"() {
2369
2477
  "use strict";
2370
2478
  init_config();
2371
- STORE_PATH = join8(SWARM_HOME, "agent.json");
2479
+ STORE_PATH = join9(SWARM_HOME, "agent.json");
2372
2480
  base = (api) => api.replace(/\/$/, "");
2373
2481
  TOKEN_PROBE_TIMEOUT_MS = 8e3;
2374
2482
  }
@@ -2619,13 +2727,13 @@ var init_daemonLink = __esm({
2619
2727
  });
2620
2728
 
2621
2729
  // src/pauseState.ts
2622
- import { existsSync as existsSync9, readFileSync as readFileSync10, renameSync as renameSync4, unlinkSync as unlinkSync5, writeFileSync as writeFileSync9 } from "fs";
2623
- import { join as join16 } from "path";
2730
+ import { existsSync as existsSync10, readFileSync as readFileSync10, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync9 } from "fs";
2731
+ import { join as join17 } from "path";
2624
2732
  function pauseStatePath(home) {
2625
- return join16(home, "pause.json");
2733
+ return join17(home, "pause.json");
2626
2734
  }
2627
2735
  function loadPauseState(home, io = {}) {
2628
- const exists = io.exists ?? existsSync9;
2736
+ const exists = io.exists ?? existsSync10;
2629
2737
  const readFile2 = io.readFile ?? ((p) => readFileSync10(p, "utf8"));
2630
2738
  const path = pauseStatePath(home);
2631
2739
  if (!exists(path)) return ACTIVE;
@@ -2645,13 +2753,13 @@ function loadPauseState(home, io = {}) {
2645
2753
  function savePauseState(home, state, io = {}) {
2646
2754
  const path = pauseStatePath(home);
2647
2755
  if (!state.paused) {
2648
- const remove = io.remove ?? ((p) => existsSync9(p) && unlinkSync5(p));
2756
+ const remove = io.remove ?? ((p) => existsSync10(p) && unlinkSync5(p));
2649
2757
  remove(path);
2650
2758
  return;
2651
2759
  }
2652
2760
  const writeFile3 = io.writeFile ?? ((p, data) => {
2653
2761
  writeFileSync9(`${p}.tmp`, data);
2654
- renameSync4(`${p}.tmp`, p);
2762
+ renameSync5(`${p}.tmp`, p);
2655
2763
  });
2656
2764
  writeFile3(path, JSON.stringify(state, null, 2));
2657
2765
  }
@@ -2687,7 +2795,7 @@ var init_pauseState = __esm({
2687
2795
  });
2688
2796
 
2689
2797
  // src/heartbeat.ts
2690
- import { readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync10 } from "fs";
2798
+ import { readFileSync as readFileSync11, renameSync as renameSync6, writeFileSync as writeFileSync10 } from "fs";
2691
2799
  function readHeartbeat(path) {
2692
2800
  try {
2693
2801
  const ms = Number(readFileSync11(path, "utf8").trim());
@@ -2708,7 +2816,8 @@ __export(db_exports, {
2708
2816
  SwarmDb: () => SwarmDb
2709
2817
  });
2710
2818
  import { createRequire as createRequire2 } from "module";
2711
- import { existsSync as existsSync10 } from "fs";
2819
+ import { randomUUID as randomUUID3 } from "crypto";
2820
+ import { existsSync as existsSync11 } from "fs";
2712
2821
  import { resolve as resolve2 } from "path";
2713
2822
  function rowToQuestion(row) {
2714
2823
  return {
@@ -2870,6 +2979,12 @@ CREATE INDEX IF NOT EXISTS idx_runs_area ON runs(area);
2870
2979
  CREATE INDEX IF NOT EXISTS idx_questions_run ON questions(run_id);
2871
2980
  CREATE INDEX IF NOT EXISTS idx_questions_status ON questions(status);
2872
2981
  CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id);
2982
+ -- \u043A\u043B\u044E\u0447-\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u0440\u043E \u0421\u0410\u041C \u0424\u0410\u0419\u041B \u0431\u0430\u0437\u044B (\u043D\u0435 \u043F\u0440\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u044B). \u041F\u043E\u043A\u0430 \u0432 \u043D\u0451\u043C \u043E\u0434\u043D\u0430 \u0437\u0430\u043F\u0438\u0441\u044C \u2014 instance_id
2983
+ -- (TASWM-419): \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u042D\u0422\u041E\u0419 \u0436\u0438\u0437\u043D\u0438 \u0431\u0430\u0437\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u043C \u043D\u0435\u0439\u043C\u0441\u043F\u0435\u0439\u0441\u044F\u0442\u0441\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438 run-log'\u043E\u0432.
2984
+ CREATE TABLE IF NOT EXISTS meta (
2985
+ key TEXT PRIMARY KEY,
2986
+ value TEXT NOT NULL
2987
+ );
2873
2988
  -- \u043C\u0430\u0440\u043A\u0435\u0440 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u044B\u0445 \u0438\u043C\u043F\u043E\u0440\u0442\u043E\u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0438\u0437 \u0434\u0440\u0443\u0433\u0438\u0445 \u0411\u0414 (\u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C importDb)
2874
2989
  CREATE TABLE IF NOT EXISTS imports (
2875
2990
  source TEXT PRIMARY KEY,
@@ -2882,6 +2997,8 @@ CREATE TABLE IF NOT EXISTS imports (
2882
2997
  defaultRepo;
2883
2998
  databaseCtor;
2884
2999
  // для importDb (второй Database, source)
3000
+ /** Идентификатор этой жизни файла базы (TASWM-419) — им неймспейсятся каталоги run-log'ов. */
3001
+ instanceId;
2885
3002
  constructor(path = "swarm.db", defaultRepo = "") {
2886
3003
  this.defaultRepo = normalizeRepo(defaultRepo);
2887
3004
  this.databaseCtor = loadBetterSqlite3();
@@ -2949,6 +3066,22 @@ CREATE TABLE IF NOT EXISTS imports (
2949
3066
  if (this.defaultRepo) {
2950
3067
  this.db.prepare("UPDATE runs SET repo = ? WHERE repo IS NULL").run(this.defaultRepo);
2951
3068
  }
3069
+ this.instanceId = this.ensureInstanceId();
3070
+ }
3071
+ /**
3072
+ * Идентификатор ЭТОЙ жизни файла базы (TASWM-419). Заводится при первом открытии и живёт
3073
+ * в самой базе, поэтому переживает рестарт демона, но НЕ переживает пересоздание файла:
3074
+ * снесли базу — новый id, и каталоги run-log'ов нового прогона #1 не подселяются к файлам
3075
+ * прогона #1 из прошлой жизни. Именно это и было корнем «лог не того прогона»: номер там
3076
+ * был вся информация об адресе.
3077
+ */
3078
+ ensureInstanceId() {
3079
+ const read = () => this.db.prepare("SELECT value FROM meta WHERE key = 'instance_id'").get()?.value;
3080
+ const existing = read();
3081
+ if (existing) return existing;
3082
+ const fresh = randomUUID3().replace(/-/g, "").slice(0, 12);
3083
+ this.db.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES ('instance_id', ?)").run(fresh);
3084
+ return read() ?? fresh;
2952
3085
  }
2953
3086
  close() {
2954
3087
  this.db.close();
@@ -3043,6 +3176,15 @@ CREATE TABLE IF NOT EXISTS imports (
3043
3176
  const m = /^local:(\d+)$/.exec(key);
3044
3177
  return m ? this.getRun(Number(m[1])) : null;
3045
3178
  }
3179
+ /**
3180
+ * TASWM-419: адрес каталога run-log'ов — номер, НЕЙМСПЕЙСНУТЫЙ идентификатором файла базы
3181
+ * (`local:<instance>:<id>`). Тот же номер в пересозданной базе даёт другой instance, то
3182
+ * есть другой каталог: подселиться к файлам чужого прогона с тем же номером больше нечем.
3183
+ * Псевдонимов у sqlite-прогона нет — ключ не меняется, пока жив файл базы.
3184
+ */
3185
+ runLogKeys(id) {
3186
+ return this.getRun(id) ? [`local:${this.instanceId}:${id}`] : [];
3187
+ }
3046
3188
  listRuns(status) {
3047
3189
  const rows = status ? this.db.prepare("SELECT * FROM runs WHERE status = ? ORDER BY id DESC").all(status) : this.db.prepare("SELECT * FROM runs ORDER BY id DESC").all();
3048
3190
  return rows.map(rowToRun);
@@ -3283,7 +3425,7 @@ CREATE TABLE IF NOT EXISTS imports (
3283
3425
  const repoN = normalizeRepo(repo);
3284
3426
  const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
3285
3427
  if (already) return { imported: false, runs: already.run_count };
3286
- if (!existsSync10(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
3428
+ if (!existsSync11(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
3287
3429
  const src = new this.databaseCtor(abs);
3288
3430
  try {
3289
3431
  const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
@@ -3610,8 +3752,8 @@ CREATE TABLE IF NOT EXISTS imports (
3610
3752
  });
3611
3753
 
3612
3754
  // src/repoScan.ts
3613
- import { readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
3614
- import { join as join17 } from "path";
3755
+ import { readdirSync as readdirSync9, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
3756
+ import { join as join18 } from "path";
3615
3757
  var init_repoScan = __esm({
3616
3758
  "src/repoScan.ts"() {
3617
3759
  "use strict";
@@ -3621,8 +3763,8 @@ var init_repoScan = __esm({
3621
3763
 
3622
3764
  // src/repoDiscovery.ts
3623
3765
  import { homedir as homedir4 } from "os";
3624
- import { readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
3625
- import { isAbsolute as isAbsolute3, join as join18, resolve as resolve3 } from "path";
3766
+ import { readFileSync as readFileSync13, renameSync as renameSync7, writeFileSync as writeFileSync11 } from "fs";
3767
+ import { isAbsolute as isAbsolute3, join as join19, resolve as resolve3 } from "path";
3626
3768
  function repoMapLines(rows) {
3627
3769
  if (!rows?.length) return [];
3628
3770
  const missing = rows.filter((r) => !r.path).length;
@@ -3635,18 +3777,24 @@ function repoMapLines(rows) {
3635
3777
  });
3636
3778
  return [head, ...body];
3637
3779
  }
3638
- function readRepoMapCache(path) {
3780
+ function loadRepoMapCache(path) {
3639
3781
  try {
3640
3782
  const raw = JSON.parse(readFileSync13(path, "utf8"));
3641
- if (!Array.isArray(raw?.entries)) return [];
3642
- return raw.entries.filter(
3643
- (e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
3644
- );
3783
+ if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
3784
+ return {
3785
+ scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
3786
+ entries: raw.entries.filter(
3787
+ (e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
3788
+ )
3789
+ };
3645
3790
  } catch {
3646
- return [];
3791
+ return { scannedAt: null, entries: [] };
3647
3792
  }
3648
3793
  }
3649
- var SOURCE_TEXT;
3794
+ function readRepoMapCache(path) {
3795
+ return loadRepoMapCache(path).entries;
3796
+ }
3797
+ var REPO_SCAN_TTL_MS, UNKNOWN_LIST_SCAN_TTL_MS, SOURCE_TEXT;
3650
3798
  var init_repoDiscovery = __esm({
3651
3799
  "src/repoDiscovery.ts"() {
3652
3800
  "use strict";
@@ -3654,6 +3802,8 @@ var init_repoDiscovery = __esm({
3654
3802
  init_config();
3655
3803
  init_linkFailure();
3656
3804
  init_repoScan();
3805
+ REPO_SCAN_TTL_MS = 24 * 60 * 6e4;
3806
+ UNKNOWN_LIST_SCAN_TTL_MS = 60 * 6e4;
3657
3807
  SOURCE_TEXT = {
3658
3808
  configured: "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
3659
3809
  cache: "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B",
@@ -3998,10 +4148,10 @@ __export(statusCli_exports, {
3998
4148
  probeLink: () => probeLink,
3999
4149
  runStatusCli: () => runStatusCli
4000
4150
  });
4001
- import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
4151
+ import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
4002
4152
  function outboxPending(path) {
4003
4153
  try {
4004
- if (!existsSync11(path)) return 0;
4154
+ if (!existsSync12(path)) return 0;
4005
4155
  return readFileSync14(path, "utf8").split("\n").filter((l) => l.trim()).length;
4006
4156
  } catch {
4007
4157
  return null;
@@ -4140,8 +4290,8 @@ var init_statusCli = __esm({
4140
4290
  });
4141
4291
 
4142
4292
  // src/platform.ts
4143
- import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
4144
- import { delimiter, join as join19 } from "path";
4293
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
4294
+ import { delimiter, join as join20 } from "path";
4145
4295
  function safeReadText(path) {
4146
4296
  try {
4147
4297
  return readFileSync15(path, "utf8");
@@ -4150,13 +4300,13 @@ function safeReadText(path) {
4150
4300
  }
4151
4301
  }
4152
4302
  function resolveCommandPath(name, env = process.env, platform = process.platform) {
4153
- if (name.includes("/") || name.includes("\\")) return existsSync12(name) ? name : null;
4303
+ if (name.includes("/") || name.includes("\\")) return existsSync13(name) ? name : null;
4154
4304
  const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
4155
4305
  const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
4156
4306
  for (const dir of dirs) {
4157
4307
  for (const ext of exts) {
4158
- const candidate = join19(dir, name + ext);
4159
- if (existsSync12(candidate)) return candidate;
4308
+ const candidate = join20(dir, name + ext);
4309
+ if (existsSync13(candidate)) return candidate;
4160
4310
  }
4161
4311
  }
4162
4312
  return null;
@@ -4259,13 +4409,13 @@ var init_platform = __esm({
4259
4409
  });
4260
4410
 
4261
4411
  // src/envGate.ts
4262
- import { existsSync as existsSync13 } from "fs";
4263
- import { join as join20 } from "path";
4412
+ import { existsSync as existsSync14 } from "fs";
4413
+ import { join as join21 } from "path";
4264
4414
  import { createConnection } from "net";
4265
4415
  import { execa as execa3 } from "execa";
4266
4416
  function detectRequirements(repo, io = {}) {
4267
- const exists = io.exists ?? existsSync13;
4268
- const marker = COMPOSE_MARKERS.find((f) => exists(join20(repo, f)));
4417
+ const exists = io.exists ?? existsSync14;
4418
+ const marker = COMPOSE_MARKERS.find((f) => exists(join21(repo, f)));
4269
4419
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
4270
4420
  }
4271
4421
  async function dockerAlive() {
@@ -4297,7 +4447,7 @@ var init_envGate = __esm({
4297
4447
  });
4298
4448
 
4299
4449
  // src/doctor.ts
4300
- import { isAbsolute as isAbsolute4, join as join21 } from "path";
4450
+ import { isAbsolute as isAbsolute4, join as join22 } from "path";
4301
4451
  function ok(id, title, detail) {
4302
4452
  return { id, title, status: "ok", detail, remedies: [], critical: false };
4303
4453
  }
@@ -4504,7 +4654,7 @@ function checkRepos(io, snap) {
4504
4654
  critical: true
4505
4655
  };
4506
4656
  }
4507
- if (env && !io.exists(join21(env, ".git"))) {
4657
+ if (env && !io.exists(join22(env, ".git"))) {
4508
4658
  return {
4509
4659
  id: "repos",
4510
4660
  title,
@@ -4514,7 +4664,7 @@ function checkRepos(io, snap) {
4514
4664
  critical: true
4515
4665
  };
4516
4666
  }
4517
- const broken = snap.paths.filter((p) => !io.exists(join21(p, ".git")));
4667
+ const broken = snap.paths.filter((p) => !io.exists(join22(p, ".git")));
4518
4668
  if (broken.length) {
4519
4669
  return {
4520
4670
  id: "repos",
@@ -4679,11 +4829,11 @@ var init_doctor = __esm({
4679
4829
 
4680
4830
  // src/repoMemory.ts
4681
4831
  import { readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "fs";
4682
- import { join as join22 } from "path";
4832
+ import { join as join23 } from "path";
4683
4833
  function loadRememberedRepos(home) {
4684
4834
  let raw;
4685
4835
  try {
4686
- raw = readFileSync16(join22(home, FILE), "utf8");
4836
+ raw = readFileSync16(join23(home, FILE), "utf8");
4687
4837
  } catch {
4688
4838
  return [];
4689
4839
  }
@@ -4718,7 +4868,7 @@ __export(installCli_exports, {
4718
4868
  runInstallCli: () => runInstallCli,
4719
4869
  shortenError: () => shortenError
4720
4870
  });
4721
- import { existsSync as existsSync14 } from "fs";
4871
+ import { existsSync as existsSync15 } from "fs";
4722
4872
  function classifyChromiumError(message) {
4723
4873
  if (/playwright-core не установлен|Cannot find (module|package)/i.test(message)) return "no-module";
4724
4874
  if (/error while loading shared libraries|cannot open shared object file|missing dependencies|\blib[\w.+-]*\.so[.\d]*\b/i.test(message)) return "no-libs";
@@ -4773,7 +4923,7 @@ function defaultDoctorIO(packageVersion) {
4773
4923
  packageVersion,
4774
4924
  entry,
4775
4925
  hasCommand: (name) => hasCommandInPath(name),
4776
- exists: existsSync14,
4926
+ exists: existsSync15,
4777
4927
  run: (command) => runQuiet(command),
4778
4928
  latestVersion: () => latestMemo ??= fetchLatestVersion(),
4779
4929
  probeChromium: () => probeChromium(),
@@ -4982,13 +5132,13 @@ var init_updateCli = __esm({
4982
5132
 
4983
5133
  // src/cli.ts
4984
5134
  init_cliIo();
4985
- import { basename as basename4, join as join23 } from "path";
4986
- import { existsSync as existsSync15, readFileSync as readFileSync17, readdirSync as readdirSync9, statSync as statSync9, openSync as openSync4, readSync as readSync2, closeSync as closeSync4, rmSync as rmSync2 } from "fs";
5135
+ import { basename as basename4, dirname as dirname4, join as join24 } from "path";
5136
+ import { existsSync as existsSync16, readFileSync as readFileSync17, writeFileSync as writeFileSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync10, statSync as statSync10, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
4987
5137
  import { setTimeout as sleep2 } from "timers/promises";
4988
5138
 
4989
5139
  // src/swarmClient.ts
4990
5140
  init_config();
4991
- import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
5141
+ import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
4992
5142
  import { dirname } from "path";
4993
5143
  import { randomUUID } from "crypto";
4994
5144
 
@@ -5194,9 +5344,58 @@ function hostResourceSnapshot() {
5194
5344
  init_names2();
5195
5345
  init_storm();
5196
5346
 
5347
+ // src/repoRules.ts
5348
+ var RULE_FIELDS = ["brief", "zones", "env", "verify", "conventions", "spine"];
5349
+ function parsePendingRepoRules(raw) {
5350
+ if (!Array.isArray(raw)) return [];
5351
+ const out = [];
5352
+ for (const item of raw) {
5353
+ if (!item || typeof item !== "object") continue;
5354
+ const r = item;
5355
+ const field = RULE_FIELDS.find((f) => f === r.field);
5356
+ if (!field) continue;
5357
+ out.push({
5358
+ field,
5359
+ value: r.value,
5360
+ by: typeof r.by === "string" ? r.by : null,
5361
+ task: typeof r.task === "string" ? r.task : null,
5362
+ why: typeof r.why === "string" ? r.why : null,
5363
+ cheap: r.cheap === true,
5364
+ overwritesHuman: r.overwritesHuman === true
5365
+ });
5366
+ }
5367
+ return out;
5368
+ }
5369
+
5197
5370
  // src/logTail.ts
5198
- import { closeSync, existsSync as existsSync3, openSync, readSync, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
5371
+ import { closeSync, existsSync as existsSync4, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
5372
+ import { join as join6 } from "path";
5373
+
5374
+ // src/runLogDir.ts
5375
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync4, renameSync as renameSync2, statSync as statSync3 } from "fs";
5199
5376
  import { join as join5 } from "path";
5377
+ function runLogDirName(runLogKey) {
5378
+ const safe = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
5379
+ if (!safe) throw new Error(`\u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435 \u0433\u043E\u0434\u0438\u0442\u0441\u044F \u0432 \u0438\u043C\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430 run-log'\u043E\u0432: ${JSON.stringify(runLogKey)}`);
5380
+ return /^\d+$/.test(safe) ? `k-${safe}` : safe;
5381
+ }
5382
+ function runLogDirPath(runLogsDir, runLogKey) {
5383
+ return join5(runLogsDir, runLogDirName(runLogKey));
5384
+ }
5385
+ function findRunLogDir(runLogsDir, keys) {
5386
+ for (const key of keys) {
5387
+ const dir = runLogDirPath(runLogsDir, key);
5388
+ if (existsSync3(dir)) return dir;
5389
+ }
5390
+ return null;
5391
+ }
5392
+ function runLogDirForWrite(runLogsDir, keys) {
5393
+ const canonical = keys[0];
5394
+ if (!canonical) return null;
5395
+ return findRunLogDir(runLogsDir, keys) ?? runLogDirPath(runLogsDir, canonical);
5396
+ }
5397
+
5398
+ // src/logTail.ts
5200
5399
  var TAIL_LINES_FALLBACK = 200;
5201
5400
  var TAIL_MAX_BYTES_FALLBACK = 64 * 1024;
5202
5401
 
@@ -5283,6 +5482,11 @@ var SwarmClient = class _SwarmClient {
5283
5482
  // Живёт в памяти: рестарт демона просто переспросит прод.
5284
5483
  depsProd = /* @__PURE__ */ new Map();
5285
5484
  depsAskedAt = /* @__PURE__ */ new Map();
5485
+ // TASWM-399: ссылка dependsOn → человеко-слаг блокера, как его назвал прод. В колонке
5486
+ // лежит cuid прогона или задачи, а в лог демона, в событие прогона и в аномалию должен
5487
+ // уходить «TASWM-392», а не «cmsqqpphd011y2xtbckkut6bj» — иначе оператору всё равно
5488
+ // нечего искать. Резолв делает прод (см. resolveDepRefs), здесь только запоминаем.
5489
+ depSlug = /* @__PURE__ */ new Map();
5286
5490
  static DEPS_TTL_MS = 6e4;
5287
5491
  // TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
5288
5492
  // ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
@@ -5342,25 +5546,25 @@ var SwarmClient = class _SwarmClient {
5342
5546
  this.repoFullNames = registry.map((e) => e.repoFullName);
5343
5547
  this.repoPathByFullName = new Map(registry.map((e) => [e.repoFullName, normalizeRepo(e.path)]));
5344
5548
  this.host = opts.host ?? null;
5345
- this.repoExists = opts.repoExists ?? existsSync4;
5549
+ this.repoExists = opts.repoExists ?? existsSync5;
5346
5550
  this.questionSyncMs = opts.questionSyncMs ?? 3e3;
5347
5551
  this.executorId = opts.executorId ?? EXECUTOR.id;
5348
5552
  this.outboxPath = opts.outboxPath;
5349
5553
  this.mapPath = `${opts.outboxPath}.prodmap.json`;
5350
5554
  this.stormPath = `${opts.outboxPath}.storm.jsonl`;
5351
- mkdirSync4(dirname(this.outboxPath), { recursive: true });
5555
+ mkdirSync5(dirname(this.outboxPath), { recursive: true });
5352
5556
  this.recover();
5353
5557
  }
5354
5558
  // ── рестарт: поднять outbox (незаписанные операции) и карту runKey→prodId ─────
5355
5559
  recover() {
5356
- if (existsSync4(this.mapPath)) {
5560
+ if (existsSync5(this.mapPath)) {
5357
5561
  try {
5358
5562
  const obj = JSON.parse(readFileSync3(this.mapPath, "utf8"));
5359
5563
  for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
5360
5564
  } catch {
5361
5565
  }
5362
5566
  }
5363
- if (existsSync4(this.outboxPath)) {
5567
+ if (existsSync5(this.outboxPath)) {
5364
5568
  const txt = readFileSync3(this.outboxPath, "utf8");
5365
5569
  for (const line of txt.split("\n")) {
5366
5570
  const s = line.trim();
@@ -5381,7 +5585,7 @@ var SwarmClient = class _SwarmClient {
5381
5585
  // eventsLog ПЕРВЫМИ (до hydrate), в порядке файла (= хронологический); id
5382
5586
  // переприсваиваем локально (id событий — только порядок, не корреляция с продом).
5383
5587
  replayGlobalEvents() {
5384
- if (!existsSync4(this.stormPath)) return;
5588
+ if (!existsSync5(this.stormPath)) return;
5385
5589
  let txt;
5386
5590
  try {
5387
5591
  txt = readFileSync3(this.stormPath, "utf8");
@@ -5646,6 +5850,24 @@ var SwarmClient = class _SwarmClient {
5646
5850
  const viaProd = [...this.runs.values()].find((r) => r.runKey === `p:${prodId}` || this.prodIdByKey.get(r.runKey) === prodId);
5647
5851
  return viaProd ? this.mat(viaProd) : null;
5648
5852
  }
5853
+ /**
5854
+ * TASWM-419: адреса каталога run-log'ов прогона. КАНОНИЧЕСКИЙ — прод-вида `p:<cuid>`, как
5855
+ * только cuid известен: он один и тот же во всех жизнях демона, а локальный uuid прогона
5856
+ * после рестарта уже не воскресает (гидрированный прогон приезжает под прод-ключом).
5857
+ * Дальше — псевдонимы: uuid, под которым каталог мог завестись в окно между спавном и
5858
+ * первым флашем в прод. По ним каталог ищется, чтобы запуски одного прогона не расползлись
5859
+ * по двум каталогам (иначе cost недосчитал бы часть, а хвост показал бы не тот запуск).
5860
+ * Номер прогона в адресе не участвует вовсе — он и есть источник «лога чужого прогона».
5861
+ */
5862
+ runLogKeys(id) {
5863
+ const key = this.runs.get(id)?.runKey;
5864
+ if (!key) return [];
5865
+ const prodId = key.startsWith("p:") ? key.slice(2) : this.prodIdByKey.get(key);
5866
+ if (!prodId) return [key];
5867
+ const canonical = `p:${prodId}`;
5868
+ const aliases = [key, ...[...this.prodIdByKey.entries()].filter(([, v]) => v === prodId).map(([k]) => k)];
5869
+ return [canonical, ...new Set(aliases.filter((k) => k !== canonical))];
5870
+ }
5649
5871
  listRuns(status) {
5650
5872
  const all = [...this.runs.values()].filter((r) => status ? r.status === status : true);
5651
5873
  all.sort((a, b) => b.id - a.id);
@@ -5762,16 +5984,25 @@ var SwarmClient = class _SwarmClient {
5762
5984
  /**
5763
5985
  * TASWM-361: вердикт по каждой зависимости — см. докблок SwarmStore.depVerdicts.
5764
5986
  *
5765
- * АСИММЕТРИЯ ЗЕРКАЛА И ПРОДА, и почему она здесь намеренная. Зеркало — ЧАСТИЧНАЯ выборка
5766
- * (только прогоны этой машины, только окно гидрации), и по частичной выборке два вывода
5767
- * стоят разного:
5768
- * • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он не
5769
- * портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
5770
- * • «живых прогонов слага не осталось» — вывод от ОТСУТСТВИЯ: невидимый зеркалу прогон
5771
- * на соседней машине как раз и может быть живым. Поэтому «исчерпана неуспешно»
5772
- * принимаем ТОЛЬКО из ответа прода (refreshDeps) — он видит всех.
5773
- * Прод недоступен → вердикта нет → прогон честно ждёт следующего окна: лишняя минута
5774
- * очереди дешевле дрона, поднятого поверх работающего блокера.
5987
+ * ИСТОЧНИК ПРАВДЫ — ПРОД (TASWM-399), зеркало здесь только быстрый путь. Зеркало —
5988
+ * ЧАСТИЧНАЯ выборка (прогоны этой машины, окно гидрации), и по частичной выборке два
5989
+ * вывода стоят разного:
5990
+ * • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он
5991
+ * не портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
5992
+ * • всё остальное — вывод от ОТСУТСТВИЯ, и его принимаем ТОЛЬКО из ответа прода
5993
+ * (refreshDeps): невидимый зеркалу прогон на соседней машине как раз и может быть
5994
+ * живым, а закрытый с прода — давно смерженным.
5995
+ *
5996
+ * ССЫЛКА — НЕ ОБЯЗАТЕЛЬНО СЛАГ. В `dependsOn` лежит чаще всего cuid ПРОГОНА (так пишет
5997
+ * overmind), реже cuid задачи, и только дев-спавн `--after` пишет человеко-слаг. Быстрый
5998
+ * путь по зеркалу сравнивает `task_slug`, то есть срабатывает лишь на третьей форме —
5999
+ * это нормально: остальные две резолвит прод. А вот считать «раз в зеркале не нашёл —
6000
+ * значит не готово» было корнем TASWM-399.
6001
+ *
6002
+ * Прода ещё не спрашивали (первый тик) или он моргнул → `pending`: прогон честно ждёт
6003
+ * следующего окна. Лишняя минута очереди дешевле дрона, поднятого поверх работающего
6004
+ * блокера. `unknown` (блокера НЕ ЗНАЕТ И ПРОД) ставит только ответ прода — местным
6005
+ * незнанием такой вывод не делается.
5775
6006
  */
5776
6007
  depVerdicts(depends_on) {
5777
6008
  if (!depends_on || depends_on.length === 0) return [];
@@ -5781,57 +6012,89 @@ var SwarmClient = class _SwarmClient {
5781
6012
  doneInMirror.add(r.task_slug);
5782
6013
  }
5783
6014
  }
5784
- return depends_on.map((slug) => {
5785
- if (doneInMirror.has(slug)) {
5786
- return { slug, outcome: "done", met: true, status: "done", name: null, stopReason: null };
6015
+ return depends_on.map((ref) => {
6016
+ if (doneInMirror.has(ref)) {
6017
+ return { slug: ref, outcome: "done", met: true, status: "done", name: null, stopReason: null };
5787
6018
  }
5788
- const fromProd = this.depsProd.get(slug);
5789
- if (fromProd) return { slug, ...fromProd };
5790
- return { slug, outcome: "pending", met: false, status: null, name: null, stopReason: null };
6019
+ const fromProd = this.depsProd.get(ref);
6020
+ if (fromProd) {
6021
+ if (fromProd.outcome === "unknown" && this.knowsDepRef(ref)) {
6022
+ return { slug: this.depSlug.get(ref) ?? ref, outcome: "pending", met: false, status: null, name: null, stopReason: null };
6023
+ }
6024
+ return { slug: this.depSlug.get(ref) ?? ref, ...fromProd };
6025
+ }
6026
+ return { slug: ref, outcome: "pending", met: false, status: null, name: null, stopReason: null };
5791
6027
  });
5792
6028
  }
5793
6029
  /**
5794
- * TASWM-300: спросить ПРОД про зависимости — см. докблок SwarmStore.refreshDeps.
6030
+ * Знает ли ЗЕРКАЛО хоть один прогон по ссылке `dependsOn` (TASWM-399). Ссылка бывает
6031
+ * слагом, cuid'ом задачи и cuid'ом прогона — последний сверяем с прод-id, как это
6032
+ * делают runByProdId/refreshRun. Служебные сабраны (ревьюер/резолвер/верификатор) не
6033
+ * в счёт: их существование не означает, что жив сам блокер.
6034
+ */
6035
+ knowsDepRef(ref) {
6036
+ for (const r of this.runs.values()) {
6037
+ if (r.resolves_run_id != null || r.reviews_run_id != null || r.verifies_run_id != null) continue;
6038
+ if (r.task_slug === ref) return true;
6039
+ if (r.runKey === `p:${ref}` || this.prodIdByKey.get(r.runKey) === ref) return true;
6040
+ }
6041
+ return false;
6042
+ }
6043
+ /**
6044
+ * TASWM-300 → TASWM-399: спросить ПРОД про зависимости — см. докблок SwarmStore.refreshDeps.
5795
6045
  *
5796
- * Почему зеркала было мало. В нём лежат только прогоны ЭТОЙ машины (гейт isMine,
5797
- * hydrateRun) и только те, что попали в окно гидрации (`swarm_list_runs limit`, сортировка
5798
- * по updatedAt desc — сутки работы роя). Зависимость, закрытая на соседней машине или
5799
- * просто позавчера, в зеркало не попадала — и `--after` держал прогон в очереди вечно
5800
- * (04.08: снимали руками).
6046
+ * Почему зеркала мало. В нём лежат только прогоны ЭТОЙ машины (гейт isMine, hydrateRun) и
6047
+ * только те, что попали в окно гидрации (`swarm_list_runs limit`, сортировка по updatedAt
6048
+ * desc — сутки работы роя). Зависимость, закрытая на соседней машине, обнулившаяся при
6049
+ * рестарте демона или просто позавчерашняя, в зеркало не попадала — и `dependsOn` держал
6050
+ * прогон в очереди вечно.
6051
+ *
6052
+ * ЧЕМ СПРАШИВАЕМ И ПОЧЕМУ НЕ `swarm_list_runs` (корень TASWM-399). До этой задачи здесь
6053
+ * шёл `swarm_list_runs { taskId: <ref> }` — то есть ссылка трактовалась как ЗАДАЧА. Но в
6054
+ * `dependsOn` чаще всего лежит cuid ПРОГОНА: `resolveTaskRef` его задачей не признаёт,
6055
+ * ответ приходит `isError`, вердикт не появляется НИКОГДА, и прогон стоит молча при
6056
+ * свободной машине (12–13.08 — пять остановок роя за сутки). `swarm_deps_status` понимает
6057
+ * все три формы ссылки и отвечает по единому правилу роя (`judgeDep` на прод-стороне).
5801
6058
  *
5802
6059
  * Кеш. «Смержено» МОНОТОННО: прогон, ставший done, назад не откатывается, — поэтому
5803
6060
  * положительный ответ помним навсегда и больше не спрашиваем. Остальные вердикты (включая
5804
- * «исчерпана неуспешно», TASWM-361 — блокер могут переиграть новым прогоном) переспрашиваем
5805
- * не чаще DEPS_TTL_MS: тик идёт раз в ~4с, а ждущих зависимость прогонов может быть
5806
- * много — без TTL это был бы поход в прод на каждый слаг каждый тик.
6061
+ * «исчерпана неуспешно» и «блокера не знает никто») переспрашиваем не чаще DEPS_TTL_MS:
6062
+ * оператор вправе переиграть блокер новым прогоном, а тик идёт раз в ~4 с — без TTL это
6063
+ * был бы поход в прод на каждую ссылку каждый тик.
6064
+ *
6065
+ * Одним вызовом на все просроченные ссылки, а не по вызову на ссылку: очередь ждущих
6066
+ * бывает в десятки прогонов, и N запросов на тик — это тот же самый поход в прод, только
6067
+ * дороже.
5807
6068
  */
5808
- async refreshDeps(slugs, nowMs = Date.now()) {
5809
- for (const slug of new Set(slugs)) {
5810
- if (!slug || this.depsProd.get(slug)?.outcome === "done") continue;
5811
- const asked = this.depsAskedAt.get(slug);
5812
- if (asked !== void 0 && nowMs - asked < _SwarmClient.DEPS_TTL_MS) continue;
5813
- this.depsAskedAt.set(slug, nowMs);
5814
- let res;
5815
- try {
5816
- res = await this.call("swarm_list_runs", { taskId: slug });
5817
- } catch {
5818
- this.depsAskedAt.delete(slug);
5819
- continue;
5820
- }
5821
- if (res?.isError) continue;
5822
- const runs = res.structuredContent?.runs ?? [];
5823
- const facts = runs.slice().sort((a, b) => String(a.createdAt ?? "").localeCompare(String(b.createdAt ?? ""))).map((r) => ({
5824
- status: String(r.status ?? ""),
5825
- // Служебный сабран под тем же слагом (резолвер конфликта / ревьюер): его финал —
5826
- // не финал задачи. Резолвер вычёркивался и до TASWM-361, ревьюер добавлен вместе
5827
- // с расширением критерия — иначе остановленный ревьюер закрывал бы зависимость.
5828
- service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId) || Boolean(r.verifiesRunId),
5829
- // В проде имя дрона — колонка droneName (см. hydrateRun); `name` терпим как
5830
- // запасной вариант ради фикстур и старых ответов.
5831
- name: r.droneName ?? r.name ?? null,
5832
- stopReason: r.stopReason ?? null
5833
- }));
5834
- this.depsProd.set(slug, judgeDep(facts));
6069
+ async refreshDeps(refs, nowMs = Date.now()) {
6070
+ const ask = [...new Set(refs)].filter((ref) => {
6071
+ if (!ref || this.depsProd.get(ref)?.outcome === "done") return false;
6072
+ const asked = this.depsAskedAt.get(ref);
6073
+ return asked === void 0 || nowMs - asked >= _SwarmClient.DEPS_TTL_MS;
6074
+ });
6075
+ if (!ask.length) return;
6076
+ for (const ref of ask) this.depsAskedAt.set(ref, nowMs);
6077
+ let res;
6078
+ try {
6079
+ res = await this.call("swarm_deps_status", { refs: ask });
6080
+ } catch {
6081
+ for (const ref of ask) this.depsAskedAt.delete(ref);
6082
+ return;
6083
+ }
6084
+ if (res?.isError) return;
6085
+ const deps = res.structuredContent?.deps ?? [];
6086
+ for (const d of deps) {
6087
+ const ref = typeof d.ref === "string" ? d.ref : null;
6088
+ if (!ref) continue;
6089
+ const outcome = String(d.outcome ?? "pending");
6090
+ this.depsProd.set(ref, {
6091
+ outcome,
6092
+ met: Boolean(d.met),
6093
+ status: d.status ?? null,
6094
+ name: d.name ?? null,
6095
+ stopReason: d.stopReason ?? null
6096
+ });
6097
+ if (typeof d.slug === "string" && d.slug) this.depSlug.set(ref, d.slug);
5835
6098
  }
5836
6099
  }
5837
6100
  /**
@@ -6150,7 +6413,7 @@ var SwarmClient = class _SwarmClient {
6150
6413
  * транзиентным сбоем ОС, который этот PR должен переживать, а не превращать в мор.
6151
6414
  */
6152
6415
  reconcileRuntime(opts) {
6153
- const wtExists = opts.worktreeExists ?? existsSync4;
6416
+ const wtExists = opts.worktreeExists ?? existsSync5;
6154
6417
  const pidByWorktree = /* @__PURE__ */ new Map();
6155
6418
  try {
6156
6419
  for (const p of opts.listProcesses()) if (p.worktree) pidByWorktree.set(p.worktree, p.pid);
@@ -6460,6 +6723,25 @@ var SwarmClient = class _SwarmClient {
6460
6723
  const sc = res.structuredContent ?? {};
6461
6724
  return normalizeBaseBranch(sc.config?.baseBranch) ?? null;
6462
6725
  }
6726
+ /**
6727
+ * ПРАВИЛА ОТ ДРОНОВ, ждущие вердикта ревьюера (TASWM-431): их демон кладёт в промпт
6728
+ * ревьюера — тот решает по ним в своём же вердикте. `taskSlug` — правила ЭТОГО
6729
+ * прогона: ревьюер отвечает за находки своего дрона, а не за чужие.
6730
+ *
6731
+ * Прод недоступен / старый прод (тула нет) / ответ не той формы → пустой список:
6732
+ * ревью обязано подниматься и без этого блока — правила тут не главное.
6733
+ */
6734
+ async repoPendingRules(repoFullName, taskSlug) {
6735
+ let res;
6736
+ try {
6737
+ res = await this.call("swarm_repo_rule_pending", { repoFullName, ...taskSlug ? { taskSlug } : {} });
6738
+ } catch {
6739
+ return [];
6740
+ }
6741
+ if (res?.isError) return [];
6742
+ const sc = res.structuredContent ?? {};
6743
+ return parsePendingRepoRules(sc.rules);
6744
+ }
6463
6745
  /**
6464
6746
  * Live КОМАНДЫ ПОДГОТОВКИ worktree из Taskless (TASWM-312): тот же swarm_repo_config по
6465
6747
  * repoFullName — Project.swarmConfig.prepare. Настройка сильнее детекта демона (deps.ts):
@@ -6503,29 +6785,11 @@ var SwarmClient = class _SwarmClient {
6503
6785
  const p = sc.profile;
6504
6786
  return p && typeof p === "object" && !Array.isArray(p) ? p : null;
6505
6787
  }
6506
- /**
6507
- * РАЗВЕДКА репозитория (TASWM-382): отправить в прод то, что разведчик вычитал в коде.
6508
- * Живую разметку это НЕ меняет — предложение ложится рядом и ждёт человека; поэтому
6509
- * ошибка здесь дорога ровно на стоимость прогона разведки, и мы её возвращаем, а не
6510
- * глотаем, как чтения выше.
6511
- *
6512
- * Мимо outbox (в отличие от мутаций прогонов): предложение имеет смысл только вместе
6513
- * со свежим состоянием репозитория, и доехавшее через сутки после разведки — хуже,
6514
- * чем не доехавшее вовсе. Прод недоступен — человек перезапустит разведку.
6515
- */
6516
- async reportRepoScout(repoFullName, report, by) {
6517
- let res;
6518
- try {
6519
- res = await this.call("swarm_repo_scout_report", { repoFullName, report, ...by ? { by } : {} });
6520
- } catch (e) {
6521
- return { ok: false, error: `\u043F\u0440\u043E\u0434 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${e.message}` };
6522
- }
6523
- const text = res?.content?.map((c) => "text" in c ? c.text : "").join(" ") ?? "";
6524
- if (res?.isError) return { ok: false, error: text || "\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0438" };
6525
- const sc = res.structuredContent ?? {};
6526
- const diff = Array.isArray(sc.diff) ? sc.diff : [];
6527
- return { ok: true, diff, display: text };
6528
- }
6788
+ // TASWM-441: отправка предложения разведки в прод (`swarm_repo_scout_report`) убрана —
6789
+ // разведчик больше не предлагает разметку в базу, а ведёт файл В САМОМ репозитории
6790
+ // (docs/AGENT_BRIEF.md, см. repoScout.ts). Прод-сторона тула и экран приёмки уезжают
6791
+ // отдельной задачей TASWM-442; `repoProfile` выше — её же остаток, читателей у него
6792
+ // в демоне не осталось.
6529
6793
  /**
6530
6794
  * Live ТРЕБОВАНИЯ К ОКРУЖЕНИЮ машины из Taskless (TASWM-313): тот же swarm_repo_config по
6531
6795
  * repoFullName — Project.swarmConfig.requires. Демон проверяет их ПЕРЕД спавном дрона и не
@@ -6639,6 +6903,12 @@ var SwarmClient = class _SwarmClient {
6639
6903
  ...typeof h.paused === "boolean" ? { paused: h.paused } : {},
6640
6904
  ...typeof h.pausedAt === "string" ? { pausedAt: h.pausedAt } : {},
6641
6905
  ...typeof h.pauseReason === "string" ? { pauseReason: h.pauseReason } : {},
6906
+ // TASWM-424: «доступ отозван» — состояние ровно того же рода, что пауза, и
6907
+ // формулировка у него ОДНА на все поверхности (HOST_ACCESS_REVOKED_TEXT):
6908
+ // `hostLines` зовёт тот же `hostStateText`, что и парк машин в /swarm. Без
6909
+ // этого поля `swarm hosts` показывал отвергнутую машину просто молчащей —
6910
+ // то самое различие, ради которого флаг и заводили (TASWM-324).
6911
+ ...typeof h.accessRevoked === "boolean" ? { accessRevoked: h.accessRevoked } : {},
6642
6912
  // Показаний нет (старый прод, машина со старым демоном) — ключа не заводим вовсе,
6643
6913
  // ровно как с паузой выше: «не знаем» и так значит «не знаем».
6644
6914
  ...resources ? { resources } : {}
@@ -6724,9 +6994,18 @@ var SwarmClient = class _SwarmClient {
6724
6994
  * ВСЕГДА (в т.ч. `storm:false`), как паузу: это состояние, а не команда, и молчание тут
6725
6995
  * означало бы «прод помнит вчерашний шторм», когда рой давно работает.
6726
6996
  *
6997
+ * TASWM-402: тем же пульсом уезжают КОРНИ WORKTREE'ЕВ этой машины (`worktreeRoots` —
6998
+ * `owner/repo` → `<worktreesDir>/<repo-basename>`). Это НЕ рантайм и правило «pid и путь
6999
+ * дрона в прод не уходят» не трогает: корень — статичная конфигурация машины, того же
7000
+ * рода, что реестр репо и ёмкость, он не зависит ни от прогона, ни от живости процесса и
7001
+ * не протухает между пульсами. Прод склеивает `<корень>/<слаг>` сам — и отвечает человеку
7002
+ * на вопрос «куда идти руками», когда команда машине не доиграла (а в этом случае, по
7003
+ * определению, спросить рантайм у молчащей машины уже нельзя). Не передали корень (не
7004
+ * знаем своего каталога) → ключа нет, прод прошлое не трогает — как с capacity/resources.
7005
+ *
6727
7006
  * Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
6728
7007
  */
6729
- async heartbeat(daemonVersion, pause, capacity, resources, storm, signIn) {
7008
+ async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn, tick) {
6730
7009
  try {
6731
7010
  const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
6732
7011
  if (daemonVersion) args.daemonVersion = daemonVersion;
@@ -6744,10 +7023,18 @@ var SwarmClient = class _SwarmClient {
6744
7023
  args.storm = storm.active;
6745
7024
  if (storm.reason) args.stormReason = storm.reason;
6746
7025
  }
7026
+ if (worktreesDir) {
7027
+ const roots = {};
7028
+ for (const [full, path] of this.repoPathByFullName) roots[full] = droneWorktreeRoot(worktreesDir, path);
7029
+ if (Object.keys(roots).length) args.worktreeRoots = roots;
7030
+ }
6747
7031
  if (this.host) {
6748
7032
  args.hostId = this.host.hostId;
6749
7033
  args.hostName = this.host.hostName;
6750
7034
  }
7035
+ if (tick && Number.isFinite(tick.seq) && Number.isFinite(tick.ageMs) && tick.intervalMs > 0) {
7036
+ args.tick = { seq: Math.max(0, Math.trunc(tick.seq)), ageMs: Math.max(0, Math.trunc(tick.ageMs)), intervalMs: Math.trunc(tick.intervalMs) };
7037
+ }
6751
7038
  const res = await this.call("swarm_heartbeat", args);
6752
7039
  if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
6753
7040
  this.heartbeatOkAt = this.now();
@@ -6892,10 +7179,20 @@ var SwarmClient = class _SwarmClient {
6892
7179
  * именно путь. Не нашли в реестре (репо не наш / ручной `--repo <path>` спавн, где
6893
7180
  * repo — ВСЕГДА уже путь, старый формат) → возвращаем как есть без изменений
6894
7181
  * (обратная совместимость: раньше pr.repo ВСЕГДА был локальным путём).
7182
+ *
7183
+ * РЕГИСТР НЕ РАЗЛИЧАЕМ (TASWM-422). Ключи карты приведены к нижнему регистру
7184
+ * (parseRepoFullName — «GitHub сам не различает case в owner/repo»), а прод присылает
7185
+ * `run.repo` тем регистром, каким репозиторий записан в проекте. 17.08 это разошлось:
7186
+ * машина объявляла `ilyasmurov/ansamblist`, задача пришла с `ilyasmurov/Ansamblist`,
7187
+ * `Map.get` промахнулся — и вместо пути в прогон уехало полное имя репозитория. Дальше
7188
+ * git-операции шли в несуществующий каталог, прогон умирал за 5 секунд, а задача
7189
+ * возвращалась в очередь и бралась снова: пять павших прогонов за пять минут.
7190
+ * Возвращаем ИСХОДНУЮ строку, а не приведённую: промах бывает и на локальном пути
7191
+ * (ручной спавн), а путь регистр различает — вернув lowercase, мы сломали бы его.
6895
7192
  */
6896
7193
  resolveRepo(raw) {
6897
7194
  if (!raw) return "";
6898
- return this.repoPathByFullName.get(raw) ?? raw;
7195
+ return this.repoPathByFullName.get(raw) ?? this.repoPathByFullName.get(raw.toLowerCase()) ?? raw;
6899
7196
  }
6900
7197
  /**
6901
7198
  * TASWM-281: МОЯ ли это работа. Три исхода:
@@ -7424,6 +7721,7 @@ function withManualAreas(history, areas) {
7424
7721
 
7425
7722
  // src/cli.ts
7426
7723
  init_config();
7724
+ import { $ as $6 } from "execa";
7427
7725
 
7428
7726
  // src/daemonLaunch.ts
7429
7727
  import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync4 } from "fs";
@@ -7493,20 +7791,20 @@ function tailFile(path, lines) {
7493
7791
  }
7494
7792
 
7495
7793
  // src/logRotate.ts
7496
- import { copyFileSync, renameSync as renameSync2, statSync as statSync4, fstatSync, truncateSync, unlinkSync, existsSync as existsSync5 } from "fs";
7794
+ import { copyFileSync, renameSync as renameSync3, statSync as statSync5, fstatSync, truncateSync, unlinkSync, existsSync as existsSync6 } from "fs";
7497
7795
  var realIo = {
7498
7796
  size: (path) => {
7499
7797
  try {
7500
- return statSync4(path).size;
7798
+ return statSync5(path).size;
7501
7799
  } catch {
7502
7800
  return null;
7503
7801
  }
7504
7802
  },
7505
7803
  copy: copyFileSync,
7506
- rename: renameSync2,
7804
+ rename: renameSync3,
7507
7805
  truncate: (path) => truncateSync(path, 0),
7508
7806
  unlink: unlinkSync,
7509
- exists: existsSync5
7807
+ exists: existsSync6
7510
7808
  };
7511
7809
  var STRAY_GENERATIONS = 8;
7512
7810
  function rotateDaemonLog(path, opts, io = realIo) {
@@ -7764,18 +8062,18 @@ init_repoRegistry();
7764
8062
 
7765
8063
  // src/hostIdentity.ts
7766
8064
  import { hostname } from "os";
7767
- import { existsSync as existsSync6, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "fs";
7768
- import { join as join6 } from "path";
8065
+ import { existsSync as existsSync7, readFileSync as readFileSync5, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "fs";
8066
+ import { join as join7 } from "path";
7769
8067
  import { randomUUID as randomUUID2 } from "crypto";
7770
8068
  function hostIdentityPath(home) {
7771
- return join6(home, "host.json");
8069
+ return join7(home, "host.json");
7772
8070
  }
7773
8071
  function loadHostIdentity(deps) {
7774
- const exists = deps.exists ?? existsSync6;
8072
+ const exists = deps.exists ?? existsSync7;
7775
8073
  const readFile2 = deps.readFile ?? ((p) => readFileSync5(p, "utf8"));
7776
8074
  const writeFile3 = deps.writeFile ?? ((p, data) => {
7777
8075
  writeFileSync4(`${p}.tmp`, data);
7778
- renameSync3(`${p}.tmp`, p);
8076
+ renameSync4(`${p}.tmp`, p);
7779
8077
  });
7780
8078
  const host = deps.hostname ?? hostname;
7781
8079
  const newId = deps.newId ?? randomUUID2;
@@ -7889,8 +8187,8 @@ import { basename as basename2 } from "path";
7889
8187
  import { serve as serve2 } from "@hono/node-server";
7890
8188
 
7891
8189
  // src/diff.ts
7892
- import { statSync as statSync5 } from "fs";
7893
- import { join as join7 } from "path";
8190
+ import { statSync as statSync6 } from "fs";
8191
+ import { join as join8 } from "path";
7894
8192
  import { $ as $3 } from "execa";
7895
8193
  var MAX_FILES = 200;
7896
8194
  var MAX_LINES_PER_FILE = 1500;
@@ -8033,7 +8331,7 @@ async function resolveBase(wt, baseBranch) {
8033
8331
  }
8034
8332
  function mtime(path) {
8035
8333
  try {
8036
- return statSync5(path).mtimeMs;
8334
+ return statSync6(path).mtimeMs;
8037
8335
  } catch {
8038
8336
  return 0;
8039
8337
  }
@@ -8058,7 +8356,7 @@ async function collectRunDiff(worktree, opts = {}) {
8058
8356
  files.push(pf);
8059
8357
  }
8060
8358
  }
8061
- files.sort((a, b) => mtime(join7(worktree, b.path)) - mtime(join7(worktree, a.path)));
8359
+ files.sort((a, b) => mtime(join8(worktree, b.path)) - mtime(join8(worktree, a.path)));
8062
8360
  const truncatedFiles = files.length > MAX_FILES;
8063
8361
  if (truncatedFiles) files = files.slice(0, MAX_FILES);
8064
8362
  const added = files.reduce((s, f) => s + f.added, 0);
@@ -8081,6 +8379,17 @@ init_storm();
8081
8379
  // src/gh.ts
8082
8380
  init_src();
8083
8381
  import { $ as $4 } from "execa";
8382
+ var realRepoRunner = async (args, cwd) => (await $4({ cwd })`gh ${args}`).stdout;
8383
+ async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
8384
+ if (!branch || !repoDir) return [];
8385
+ try {
8386
+ const out = await run(["pr", "list", "--head", branch, "--state", "open", "--json", "url"], repoDir);
8387
+ const arr = JSON.parse(out);
8388
+ return Array.isArray(arr) ? arr.map((p) => p.url).filter((u) => typeof u === "string") : [];
8389
+ } catch {
8390
+ return [];
8391
+ }
8392
+ }
8084
8393
  var NO_CHECKS_STDERR = /no checks reported/i;
8085
8394
  var realChecksRunner = async (pr) => {
8086
8395
  const r = await $4({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`;
@@ -8211,13 +8520,13 @@ function deniedSinceLine(nowMs) {
8211
8520
  init_config();
8212
8521
  import { createHash, randomBytes as randomBytes2 } from "crypto";
8213
8522
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
8214
- import { join as join9 } from "path";
8523
+ import { join as join10 } from "path";
8215
8524
  var REGISTER = "/api/oauth/register";
8216
8525
  var AUTHORIZE = "/api/oauth/authorize";
8217
8526
  var TOKEN = "/api/oauth/token";
8218
8527
  var SCOPE = "openid profile email taskless:full";
8219
- var STORE_PATH2 = join9(SWARM_HOME, "oauth.json");
8220
- var CLIENT_PATH = join9(SWARM_HOME, "oauth-client.json");
8528
+ var STORE_PATH2 = join10(SWARM_HOME, "oauth.json");
8529
+ var CLIENT_PATH = join10(SWARM_HOME, "oauth-client.json");
8221
8530
  function makePkce() {
8222
8531
  const verifier = randomBytes2(48).toString("base64url");
8223
8532
  const challenge = createHash("sha256").update(verifier).digest("base64url");
@@ -9944,15 +10253,15 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
9944
10253
 
9945
10254
  // src/launcher.ts
9946
10255
  import { spawn as spawn2 } from "child_process";
9947
- import { closeSync as closeSync3, existsSync as existsSync7, mkdirSync as mkdirSync7, openSync as openSync3, readdirSync as readdirSync6 } from "fs";
10256
+ import { closeSync as closeSync3, existsSync as existsSync8, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as readdirSync7 } from "fs";
9948
10257
  import { createRequire } from "module";
9949
10258
  import { homedir as homedir3 } from "os";
9950
- import { dirname as dirname3, join as join11 } from "path";
10259
+ import { dirname as dirname3, join as join12 } from "path";
9951
10260
  import { fileURLToPath } from "url";
9952
10261
 
9953
10262
  // src/droneHome.ts
9954
- import { lstatSync, mkdirSync as mkdirSync6, readdirSync as readdirSync5, rmSync, statSync as statSync6, symlinkSync as symlinkSync2 } from "fs";
9955
- import { join as join10, relative, sep } from "path";
10263
+ import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync6, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
10264
+ import { join as join11, relative, sep } from "path";
9956
10265
  var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
9957
10266
  function droneHomeDeny(env = process.env) {
9958
10267
  const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -9970,29 +10279,29 @@ function buildDroneHome(opts) {
9970
10279
  const { realHome, droneHome } = opts;
9971
10280
  const deny = opts.deny ?? droneHomeDeny();
9972
10281
  const platform = opts.platform ?? process.platform;
9973
- mkdirSync6(droneHome, { recursive: true, mode: 448 });
9974
- const wanted = new Set(mirroredEntries(readdirSync5(realHome), deny, platform));
10282
+ mkdirSync7(droneHome, { recursive: true, mode: 448 });
10283
+ const wanted = new Set(mirroredEntries(readdirSync6(realHome), deny, platform));
9975
10284
  const rel = relative(realHome, droneHome);
9976
10285
  if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
9977
- for (const existing of readdirSync5(droneHome)) {
10286
+ for (const existing of readdirSync6(droneHome)) {
9978
10287
  if (wanted.has(existing)) continue;
9979
- const p = join10(droneHome, existing);
10288
+ const p = join11(droneHome, existing);
9980
10289
  try {
9981
10290
  if (lstatSync(p).isSymbolicLink()) rmSync(p);
9982
10291
  } catch {
9983
10292
  }
9984
10293
  }
9985
10294
  for (const name of wanted) {
9986
- const link = join10(droneHome, name);
10295
+ const link = join11(droneHome, name);
9987
10296
  try {
9988
10297
  lstatSync(link);
9989
10298
  continue;
9990
10299
  } catch {
9991
10300
  }
9992
- const target = join10(realHome, name);
10301
+ const target = join11(realHome, name);
9993
10302
  let isDir;
9994
10303
  try {
9995
- isDir = statSync6(target).isDirectory();
10304
+ isDir = statSync7(target).isDirectory();
9996
10305
  } catch {
9997
10306
  continue;
9998
10307
  }
@@ -10010,7 +10319,7 @@ var REAL_PATH_ENV_PINS = [
10010
10319
  { env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
10011
10320
  ];
10012
10321
  function realPathEnvPins(realHome) {
10013
- return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join10(realHome, ...segments)]));
10322
+ return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join11(realHome, ...segments)]));
10014
10323
  }
10015
10324
  function dronePrivatePaths(realHome, swarmHome) {
10016
10325
  return [
@@ -10122,7 +10431,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
10122
10431
  const env = { ...base3 };
10123
10432
  const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
10124
10433
  if (isolate && !base3.TURBO_CACHE_DIR) {
10125
- env.TURBO_CACHE_DIR = join11(worktree, ".turbo", "cache");
10434
+ env.TURBO_CACHE_DIR = join12(worktree, ".turbo", "cache");
10126
10435
  }
10127
10436
  Object.assign(env, droneTestEnv(droneCap, base3));
10128
10437
  for (const key of DAEMON_STATE_ENV) delete env[key];
@@ -10150,20 +10459,21 @@ var DAEMON_STATE_ENV = [
10150
10459
  "TASKLESS_TOKEN"
10151
10460
  ];
10152
10461
  function bundledRoleDir(kind) {
10153
- return join11(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
10462
+ return join12(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
10154
10463
  }
10155
10464
  function roleDir(worktree, kind) {
10156
- return join11(worktree, ".claude", "roles", kind);
10465
+ return join12(worktree, ".claude", "roles", kind);
10157
10466
  }
10158
10467
  function roleDirs(worktree, kind) {
10159
- return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync7(dir));
10468
+ return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync8(dir));
10160
10469
  }
10161
- function nextRunLogPath(runLogsDir, runId) {
10162
- const dir = join11(runLogsDir, String(runId));
10163
- mkdirSync7(dir, { recursive: true });
10470
+ function nextRunLogPath(runLogsDir, runLogKeys) {
10471
+ const dir = runLogDirForWrite(runLogsDir, runLogKeys);
10472
+ if (!dir) throw new Error("\u043D\u0435\u0447\u0435\u043C \u0430\u0434\u0440\u0435\u0441\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433 run-log'\u043E\u0432: \u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u043B\u044E\u0447\u0430 (SwarmStore.runLogKeys)");
10473
+ mkdirSync8(dir, { recursive: true });
10164
10474
  let files = [];
10165
10475
  try {
10166
- files = readdirSync6(dir);
10476
+ files = readdirSync7(dir);
10167
10477
  } catch {
10168
10478
  }
10169
10479
  let next = 0;
@@ -10171,11 +10481,11 @@ function nextRunLogPath(runLogsDir, runId) {
10171
10481
  const m = /^(\d+)\.json/.exec(f);
10172
10482
  if (m) next = Math.max(next, Number(m[1]) + 1);
10173
10483
  }
10174
- return join11(dir, `${next}.json`);
10484
+ return join12(dir, `${next}.json`);
10175
10485
  }
10176
- function openNextRunLog(runLogsDir, runId) {
10486
+ function openNextRunLog(runLogsDir, runLogKeys) {
10177
10487
  for (let attempt = 0; attempt < 50; attempt++) {
10178
- const path = nextRunLogPath(runLogsDir, runId);
10488
+ const path = nextRunLogPath(runLogsDir, runLogKeys);
10179
10489
  let out;
10180
10490
  try {
10181
10491
  out = openSync3(path, "wx");
@@ -10191,7 +10501,7 @@ function openNextRunLog(runLogsDir, runId) {
10191
10501
  }
10192
10502
  return { out, err };
10193
10503
  }
10194
- throw new Error(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043D\u0430\u0439\u0442\u0438 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0439 run-log \u0444\u0430\u0439\u043B \u0434\u043B\u044F \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${runId} (\u0433\u043E\u043D\u043A\u0430?)`);
10504
+ throw new Error(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043D\u0430\u0439\u0442\u0438 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0439 run-log \u0444\u0430\u0439\u043B \u043F\u0440\u043E\u0433\u043E\u043D\u0430 (${runLogKeys[0]}) \u2014 \u0433\u043E\u043D\u043A\u0430?`);
10195
10505
  }
10196
10506
  function makeLauncher(cfg) {
10197
10507
  return ({ worktree, run, prompt, continueSession }) => {
@@ -10254,9 +10564,9 @@ function makeLauncher(cfg) {
10254
10564
  }
10255
10565
  const realHome = cfg.droneIsolation?.realHome ?? homedir3();
10256
10566
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
10257
- if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join11(cfg.droneIsolation.swarmHome, "token"))) {
10567
+ if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join12(cfg.droneIsolation.swarmHome, "token"))) {
10258
10568
  console.error(
10259
- `[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join11(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
10569
+ `[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join12(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
10260
10570
  );
10261
10571
  }
10262
10572
  const executor = cfg.executor ?? resolveExecutor();
@@ -10283,7 +10593,13 @@ function makeLauncher(cfg) {
10283
10593
  let outFd;
10284
10594
  let errFd;
10285
10595
  if (cfg.runLogsDir) {
10286
- const fds = openNextRunLog(cfg.runLogsDir, run.id);
10596
+ const logKeys = cfg.runLogKeys?.(run) ?? [];
10597
+ if (logKeys.length === 0) {
10598
+ throw new Error(
10599
+ `\u043F\u0440\u043E\u0433\u043E\u043D\u0443 #${run.id} (${run.task_slug}) \u043D\u0435\u0447\u0435\u043C \u0430\u0434\u0440\u0435\u0441\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433 run-log'\u043E\u0432: \u0443 \u0441\u0442\u043E\u0440\u0430 \u043D\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u043B\u044E\u0447\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430. \u0414\u0440\u043E\u043D \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u0435\u043D \u2014 \u0438\u043D\u0430\u0447\u0435 \u0435\u0433\u043E \u043B\u043E\u0433 \u043B\u0451\u0433 \u0431\u044B \u0432 \u043A\u0430\u0442\u0430\u043B\u043E\u0433, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043A\u0430\u043A \u0447\u0443\u0436\u043E\u0439 (TASWM-419).`
10600
+ );
10601
+ }
10602
+ const fds = openNextRunLog(cfg.runLogsDir, logKeys);
10287
10603
  outFd = fds.out;
10288
10604
  errFd = fds.err ?? void 0;
10289
10605
  stdio = ["ignore", outFd, errFd ?? "ignore"];
@@ -10311,14 +10627,27 @@ function makeLauncher(cfg) {
10311
10627
  init_droneChannel();
10312
10628
 
10313
10629
  // src/git.ts
10314
- import { existsSync as existsSync8, readdirSync as readdirSync7, statSync as statSync7 } from "fs";
10630
+ import { existsSync as existsSync9, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
10315
10631
  import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
10316
- import { isAbsolute, join as join12, sep as sep2 } from "path";
10632
+ import { isAbsolute, join as join13, sep as sep2 } from "path";
10317
10633
  import { $ as $5 } from "execa";
10318
10634
  init_swarmFiles();
10635
+ var WorktreeBusyError = class extends Error {
10636
+ constructor(branch, worktreePath) {
10637
+ super(`worktree \u0437\u0430\u043D\u044F\u0442: ${worktreePath} (\u0432\u0435\u0442\u043A\u0430 ${branch}) \u2014 \u043F\u0440\u0438\u0431\u0435\u0440\u0438 worktree \u0438\u043B\u0438 \u043E\u0442\u043C\u0435\u043D\u0438 \u043F\u0440\u043E\u0433\u043E\u043D (swarm cancel ${branch})`);
10638
+ this.branch = branch;
10639
+ this.worktreePath = worktreePath;
10640
+ this.name = "WorktreeBusyError";
10641
+ }
10642
+ branch;
10643
+ worktreePath;
10644
+ };
10645
+ async function reclaimStaleWorktree(repo, worktree) {
10646
+ await detachWorktree(repo, worktree);
10647
+ }
10319
10648
  function isLinkedWorktree(dir) {
10320
10649
  try {
10321
- return statSync7(join12(dir, ".git")).isFile();
10650
+ return statSync8(join13(dir, ".git")).isFile();
10322
10651
  } catch {
10323
10652
  return false;
10324
10653
  }
@@ -10330,7 +10659,7 @@ async function detachWorktree(repo, worktree) {
10330
10659
  }
10331
10660
  }
10332
10661
  async function ensureWorktreeDirGone(worktree) {
10333
- if (!existsSync8(worktree)) return false;
10662
+ if (!existsSync9(worktree)) return false;
10334
10663
  if (!isLinkedWorktree(worktree)) {
10335
10664
  console.error(
10336
10665
  `[git] ${worktree} \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u0442\u0451\u0433\u043D\u0443\u0442\u044B\u0439 worktree (.git \u043D\u0435 \u0444\u0430\u0439\u043B): \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u041D\u0415 \u0442\u0440\u043E\u0433\u0430\u044E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E worktree \u0434\u0440\u043E\u043D\u0430 \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0433\u043E \u0440\u0443\u043A\u0430\u043C\u0438; \u0435\u0441\u043B\u0438 \u044D\u0442\u043E \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u043D\u0430\u0447\u0438\u0442 \u0432\u0435\u0442\u043A\u0430 \u0441\u043B\u0430\u0433\u0430 \u0447\u0435\u043A\u043D\u0443\u0442\u0430 \u0432 \u043D\u0451\u043C (git checkout <slug>) \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0435\u0433\u043E \u043D\u0430 \u0434\u0440\u0443\u0433\u0443\u044E \u0432\u0435\u0442\u043A\u0443.`
@@ -10345,17 +10674,164 @@ async function ensureWorktreeDirGone(worktree) {
10345
10674
  );
10346
10675
  return true;
10347
10676
  }
10348
- if (existsSync8(worktree)) {
10677
+ if (existsSync9(worktree)) {
10349
10678
  console.error(`[git] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree ${worktree} \u043F\u0435\u0440\u0435\u0436\u0438\u043B rm \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0441\u043F\u0430\u0432\u043D \u0441\u043B\u0430\u0433\u0430 \u0432\u0441\u0442\u0430\u043D\u0435\u0442.`);
10350
10679
  }
10351
10680
  return true;
10352
10681
  }
10682
+ async function ensureLongPaths(repo, opts = {}) {
10683
+ if ((opts.platform ?? process.platform) !== "win32") return false;
10684
+ const cur = (await $5({ reject: false })`git -C ${repo} config --get core.longpaths`).stdout.trim();
10685
+ if (cur === "true") return false;
10686
+ const res = await $5({ reject: false })`git -C ${repo} config core.longpaths true`;
10687
+ if (res.exitCode !== 0) {
10688
+ console.error(
10689
+ `[git] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C core.longpaths \u0432 ${repo} \u2014 \u043D\u0430 Windows \u0441\u043D\u043E\u0441 worktree \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 \u043F\u0430\u0434\u0430\u0442\u044C \xABFilename too long\xBB \u043D\u0430 node_modules. \u0412\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438: \`git -C ${repo} config core.longpaths true\`.`
10690
+ );
10691
+ return false;
10692
+ }
10693
+ console.log(`[git] core.longpaths=true \u0432\u043A\u043B\u044E\u0447\u0451\u043D \u0434\u043B\u044F ${repo} (TASWM-279): \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u0441\u043D\u043E\u0441 worktree \u0434\u0440\u043E\u043D\u0430 \u043F\u0430\u0434\u0430\u0435\u0442 \u043D\u0430 \u0434\u043B\u0438\u043D\u043D\u044B\u0445 \u043F\u0443\u0442\u044F\u0445 node_modules`);
10694
+ return true;
10695
+ }
10696
+ async function revParse(repo, ref) {
10697
+ return (await $5({ reject: false })`git -C ${repo} rev-parse --verify --quiet ${ref}`).stdout.trim();
10698
+ }
10699
+ async function worktreeForBranch(repo, branch) {
10700
+ const out = (await $5`git -C ${repo} worktree list --porcelain`).stdout;
10701
+ const target = `refs/heads/${branch}`;
10702
+ let current = null;
10703
+ for (const line of out.split("\n")) {
10704
+ if (line.startsWith("worktree ")) current = line.slice("worktree ".length).trim();
10705
+ else if (line.startsWith("branch ") && line.slice("branch ".length).trim() === target) return current;
10706
+ }
10707
+ return null;
10708
+ }
10709
+ async function addWorktree(repo, path, branch, opts = {}) {
10710
+ const res = await checkoutWorktree(repo, path, branch, opts);
10711
+ await ensureWorktreeHygiene(path);
10712
+ return res;
10713
+ }
10714
+ async function checkoutWorktree(repo, path, branch, opts) {
10715
+ await ensureLongPaths(repo);
10716
+ const localSha = await revParse(repo, `refs/heads/${branch}`);
10717
+ const originSha = await revParse(repo, `refs/remotes/origin/${branch}`);
10718
+ if (!localSha && !originSha) {
10719
+ const freshBase = opts.base ?? await freshWorktreeBase(repo);
10720
+ await $5`git -C ${repo} worktree add -b ${branch} ${path} ${freshBase}`;
10721
+ return { reused: false };
10722
+ }
10723
+ const busyAt = await worktreeForBranch(repo, branch);
10724
+ if (busyAt) {
10725
+ const exists = existsSync9(busyAt);
10726
+ const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
10727
+ if (live) throw new WorktreeBusyError(branch, busyAt);
10728
+ if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
10729
+ await reclaimStaleWorktree(repo, busyAt);
10730
+ opts.onStaleReclaim?.(busyAt);
10731
+ }
10732
+ if (originSha) {
10733
+ await $5`git -C ${repo} worktree add -B ${branch} ${path} ${`origin/${branch}`}`;
10734
+ return { reused: true, from: "origin", sha: originSha };
10735
+ }
10736
+ await $5`git -C ${repo} worktree add ${path} ${branch}`;
10737
+ return { reused: true, from: "local" };
10738
+ }
10739
+ async function freshWorktreeBase(repo) {
10740
+ const hasOrigin = (await $5({ reject: false })`git -C ${repo} remote get-url origin`).exitCode === 0;
10741
+ if (!hasOrigin) return "HEAD";
10742
+ await $5`git -C ${repo} fetch --quiet origin`;
10743
+ return remoteMainRef(repo);
10744
+ }
10745
+ async function setWorktreeIdentity(worktree, name) {
10746
+ const local = name.toLowerCase().replace(/\s+/g, "-");
10747
+ await $5`git -C ${worktree} config extensions.worktreeConfig true`;
10748
+ await $5`git -C ${worktree} config --worktree user.name ${`${name} (swarm drone)`}`;
10749
+ await $5`git -C ${worktree} config --worktree user.email ${`${local}@drones.swarm.local`}`;
10750
+ }
10751
+ async function ensureGitExcluded(worktree, pattern) {
10752
+ try {
10753
+ const commonDir = await gitPath(worktree, "--git-common-dir");
10754
+ if (!commonDir) return false;
10755
+ const infoDir = join13(commonDir, "info");
10756
+ const excludeFile = join13(infoDir, "exclude");
10757
+ let current = "";
10758
+ try {
10759
+ current = await readFile(excludeFile, "utf8");
10760
+ } catch {
10761
+ }
10762
+ if (current.split(/\r?\n/).some((l) => l.trim() === pattern)) return true;
10763
+ await mkdir(infoDir, { recursive: true });
10764
+ const prefix = current && !current.endsWith("\n") ? "\n" : "";
10765
+ await appendFile(excludeFile, `${prefix}${pattern}
10766
+ `);
10767
+ return true;
10768
+ } catch {
10769
+ return false;
10770
+ }
10771
+ }
10772
+ async function gitPath(worktree, flag) {
10773
+ const out = await $5({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
10774
+ const raw = out.stdout.trim();
10775
+ if (out.exitCode !== 0 || !raw) return null;
10776
+ return isAbsolute(raw) ? raw : join13(worktree, raw);
10777
+ }
10778
+ async function ensureWorktreeHygiene(worktree) {
10779
+ for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
10780
+ await installGuardHooks(worktree);
10781
+ }
10782
+ async function installGuardHooks(worktree) {
10783
+ try {
10784
+ const gitDir = await gitPath(worktree, "--git-dir");
10785
+ if (!gitDir) return false;
10786
+ const hooksDir = join13(gitDir, "swarm-hooks");
10787
+ const prev = await previousHooksPath(worktree);
10788
+ await mkdir(hooksDir, { recursive: true });
10789
+ for (const hook of GUARD_HOOKS) {
10790
+ await writeFile(join13(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
10791
+ await chmod(join13(hooksDir, hook), 493);
10792
+ }
10793
+ await $5`git -C ${worktree} config extensions.worktreeConfig true`;
10794
+ await $5`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
10795
+ return true;
10796
+ } catch {
10797
+ return false;
10798
+ }
10799
+ }
10800
+ function posix(p) {
10801
+ return (sep2 === "\\" ? p.replace(/\\/g, "/") : p).replace(/\/+$/, "");
10802
+ }
10803
+ async function previousHooksPath(worktree) {
10804
+ for (const scope of ["--local", "--global", "--system"]) {
10805
+ const out = await $5({ reject: false })`git -C ${worktree} config ${scope} --get core.hooksPath`;
10806
+ const value = out.stdout.trim();
10807
+ if (out.exitCode === 0 && value) return value;
10808
+ }
10809
+ const commonDir = await gitPath(worktree, "--git-common-dir");
10810
+ return commonDir ? join13(commonDir, "hooks") : null;
10811
+ }
10353
10812
  async function removeWorktree(repo, path) {
10354
10813
  const branch = (await $5({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
10355
10814
  await cleanupWorktreeProcesses(path);
10356
10815
  await detachWorktree(repo, path);
10357
10816
  if (branch) await $5({ reject: false })`git -C ${repo} branch -D ${branch}`;
10358
10817
  }
10818
+ async function worktreeDirty(path) {
10819
+ const st = await $5({ reject: false })`git -C ${path} status --porcelain`;
10820
+ if (st.exitCode !== 0) return true;
10821
+ const status = st.stdout.trim();
10822
+ if (status) return true;
10823
+ const unpushed = (await $5({ reject: false })`git -C ${path} rev-list --count HEAD --not --remotes`).stdout.trim();
10824
+ return unpushed !== "" && unpushed !== "0";
10825
+ }
10826
+ async function remoteMainRef(repo) {
10827
+ const head = (await $5({ reject: false })`git -C ${repo} symbolic-ref --quiet --short refs/remotes/origin/HEAD`).stdout.trim();
10828
+ if (head) return head;
10829
+ for (const cand of ["origin/main", "origin/master"]) {
10830
+ const ok2 = await $5({ reject: false })`git -C ${repo} rev-parse --verify --quiet ${cand}`;
10831
+ if (ok2.exitCode === 0) return cand;
10832
+ }
10833
+ throw new Error(`\u043D\u0435 \u043D\u0430\u0448\u0451\u043B \u0434\u0435\u0444\u043E\u043B\u0442-\u0432\u0435\u0442\u043A\u0443 origin \u0432 ${repo} (\u043D\u0435\u0442 origin/HEAD, origin/main, origin/master)`);
10834
+ }
10359
10835
  var GITHUB_REMOTE_RE = /github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?\/?$/i;
10360
10836
  async function remoteRepoFullName(repo) {
10361
10837
  const url = (await $5({ reject: false })`git -C ${repo} remote get-url origin`).stdout.trim();
@@ -10373,7 +10849,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
10373
10849
 
10374
10850
  // src/attachments.ts
10375
10851
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
10376
- import { join as join13 } from "path";
10852
+ import { join as join14 } from "path";
10377
10853
  init_swarmFiles();
10378
10854
  var ATTACHMENTS_DIR = `${SWARM_TASK_DIR}/attachments`;
10379
10855
  var ATTACH_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
@@ -10385,7 +10861,7 @@ var KNOWN_EXT = /* @__PURE__ */ new Set([...IMAGE_EXT, ...DOC_EXT, ...TEXT_EXT])
10385
10861
 
10386
10862
  // src/shotUpload.ts
10387
10863
  import { readFile as fsReadFile } from "fs/promises";
10388
- import { basename as basename3, isAbsolute as isAbsolute2, join as join14 } from "path";
10864
+ import { basename as basename3, isAbsolute as isAbsolute2, join as join15 } from "path";
10389
10865
  var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
10390
10866
 
10391
10867
  // src/taskless.ts
@@ -10396,8 +10872,8 @@ init_linkFailure();
10396
10872
  // src/workspace.ts
10397
10873
  init_config();
10398
10874
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4 } from "fs";
10399
- import { join as join15 } from "path";
10400
- var WORKSPACE_PATH = join15(SWARM_HOME, "workspace.json");
10875
+ import { join as join16 } from "path";
10876
+ var WORKSPACE_PATH = join16(SWARM_HOME, "workspace.json");
10401
10877
  function loadWorkspace() {
10402
10878
  try {
10403
10879
  const w = JSON.parse(readFileSync9(WORKSPACE_PATH, "utf8"));
@@ -10602,180 +11078,200 @@ function resilientSwarmMcp(opts = {}) {
10602
11078
 
10603
11079
  // src/repoScout.ts
10604
11080
  import { HEADLESS_BLOCK } from "./overmind.js";
10605
- var SCOUT_FIELDS = ["brief", "zones", "env", "verify", "conventions", "spine"];
10606
- var SHAPE = `{
10607
- "brief": "\u0427\u0442\u043E \u044D\u0442\u043E \u0437\u0430 \u043F\u0440\u043E\u0434\u0443\u043A\u0442 \u0438 \u043D\u0430 \u0447\u0451\u043C \u043D\u0430\u043F\u0438\u0441\u0430\u043D \u2014 3-5 \u0441\u0442\u0440\u043E\u043A, \u0447\u0442\u043E\u0431\u044B \u0434\u0440\u043E\u043D \u043D\u0435 \u0432\u044B\u0432\u043E\u0434\u0438\u043B \u044D\u0442\u043E \u0438\u0437 \u043A\u043E\u0434\u0430 \u0437\u0430\u043D\u043E\u0432\u043E.",
10608
- "zones": [
10609
- { "label": "area:<\u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0435-\u0438\u043C\u044F>", "about": "\u0447\u0442\u043E \u044D\u0442\u043E \u0437\u0430 \u0437\u043E\u043D\u0430 \u043E\u0434\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439", "paths": ["packages/api/"] }
10610
- ],
10611
- "env": {
10612
- "database": "\u0447\u0435\u043C \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0431\u0430\u0437\u0443, \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430",
10613
- "migrations": "\u0447\u0435\u043C \u043D\u0430\u043A\u0430\u0442\u0438\u0442\u044C \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438, \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430",
10614
- "vars": ["\u0418\u041C\u0415\u041D\u0410", "\u041F\u0415\u0420\u0415\u041C\u0415\u041D\u041D\u042B\u0425", "\u0411\u0415\u0417_\u0417\u041D\u0410\u0427\u0415\u041D\u0418\u0419"]
10615
- },
10616
- "verify": {
10617
- "commands": ["pnpm typecheck", "pnpm test"],
10618
- "green": "\u0447\u0442\u043E \u0437\u0434\u0435\u0441\u044C \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C \u0441\u043B\u043E\u0432\u0430\u043C\u0438: \u0447\u0442\u043E \u0433\u043E\u043D\u044F\u0435\u0442\u0441\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, \u0447\u0442\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 CI, \u043A\u0430\u043A\u043E\u0439 \u0447\u0435\u043A \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u0438\u0437\u043D\u0430\u043A"
10619
- },
10620
- "conventions": { "commitLanguage": "ru|en", "slugInCommit": true, "baseBranch": "main" },
10621
- "spine": ["packages/api/prisma/schema.prisma", "packages/shared/src/"]
10622
- }`;
11081
+ var BRIEF_PATH = "docs/AGENT_BRIEF.md";
11082
+ var CLAUDE_MD_PATH = "CLAUDE.md";
11083
+ var BRIEF_IMPORT = `@${BRIEF_PATH}`;
11084
+ var BRIEF_HEADER = [
11085
+ "<!-- \u042D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B \u0432\u0435\u0434\u0451\u0442 \u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A \u0440\u043E\u044F (`swarm scout`). \u041F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u043F\u0440\u043E\u0445\u043E\u0434 \u041F\u0415\u0420\u0415\u0417\u0410\u041F\u0418\u0421\u042B\u0412\u0410\u0415\u0422 \u0435\u0433\u043E \u0446\u0435\u043B\u0438\u043A\u043E\u043C:",
11086
+ " \u043F\u0440\u0430\u0432\u043A\u0438 \u0440\u0443\u043A\u0430\u043C\u0438 \u0437\u0434\u0435\u0441\u044C \u043D\u0435 \u043F\u0435\u0440\u0435\u0436\u0438\u0432\u0443\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0443\u044E \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0443. \u0421\u0432\u043E\u0438 \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u043F\u0438\u0448\u0438 \u0432 CLAUDE.md \u2014",
11087
+ ` \u043E\u043D \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \`${BRIEF_IMPORT}\` \u0438 \u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A\u043E\u043C \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0435\u0442\u0441\u044F. -->`
11088
+ ].join("\n");
11089
+ var SHAPE = `# \u0411\u0440\u0438\u0444 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F
11090
+
11091
+ \u041A\u043E\u0440\u043E\u0442\u043A\u0438\u0439 \u043E\u0442\u0432\u0435\u0442 \u043D\u0430 \xAB\u0447\u0442\u043E \u044D\u0442\u043E \u0438 \u043D\u0430 \u0447\u0451\u043C \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043E\xBB \u2014 3-5 \u0441\u0442\u0440\u043E\u043A, \u0447\u0442\u043E\u0431\u044B \u0430\u0433\u0435\u043D\u0442 \u043D\u0435 \u0432\u044B\u0432\u043E\u0434\u0438\u043B \u044D\u0442\u043E \u0438\u0437 \u043A\u043E\u0434\u0430 \u0437\u0430\u043D\u043E\u0432\u043E.
11092
+
11093
+ ## \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435
11094
+
11095
+ - **\u0411\u0430\u0437\u0430 \u0434\u0430\u043D\u043D\u044B\u0445:** \u0447\u0435\u043C \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u2014 \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430, \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u0430\u044F \u0438\u0437 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F
11096
+ - **\u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438:** \u0447\u0435\u043C \u043D\u0430\u043A\u0430\u0442\u0438\u0442\u044C \u2014 \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430
11097
+ - **\u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F:** \`\u0418\u041C\u0415\u041D\u0410\`, \`\u0411\u0415\u0417\`, \`\u0417\u041D\u0410\u0427\u0415\u041D\u0418\u0419\`
11098
+
11099
+ ## \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438
11100
+
11101
+ - \`\u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0442\u0438\u043F\u043E\u0432\`
11102
+ - \`\u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0442\u0435\u0441\u0442\u043E\u0432\`
11103
+
11104
+ \u0427\u0442\u043E \u0437\u0434\u0435\u0441\u044C \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C: \u0441\u043B\u043E\u0432\u0430\u043C\u0438 \u2014 \u0447\u0442\u043E \u0433\u043E\u043D\u044F\u0435\u0442\u0441\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, \u0447\u0442\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 CI, \u043A\u0430\u043A\u043E\u0439 \u0447\u0435\u043A \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u0438\u0437\u043D\u0430\u043A.
11105
+
11106
+ ## \u0421\u043E\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u044F
11107
+
11108
+ - \u042F\u0437\u044B\u043A \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432: ru/en
11109
+ - \u0421\u043B\u0430\u0433 \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438 \u043A\u043E\u043C\u043C\u0438\u0442\u0430: \u043D\u0443\u0436\u0435\u043D/\u043D\u0435 \u043D\u0443\u0436\u0435\u043D
11110
+ - \u0411\u0430\u0437\u043E\u0432\u0430\u044F \u0432\u0435\u0442\u043A\u0430: main
11111
+
11112
+ ## \u0417\u043E\u043D\u044B \u043A\u043E\u0434\u0430
11113
+
11114
+ - \`packages/api/\` \u2014 \u0447\u0442\u043E \u044D\u0442\u043E \u0437\u0430 \u0437\u043E\u043D\u0430 \u043E\u0434\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439
11115
+ - \`packages/web/\` \u2014 \u2026
11116
+
11117
+ ## \u041E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442
11118
+
11119
+ \u0424\u0430\u0439\u043B\u044B \u0438 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043A\u0430\u0442\u044F\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C\u0438 PR \u0438 \u043D\u0435 \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044F\u0442\u0441\u044F:
11120
+
11121
+ - \`packages/api/prisma/schema.prisma\`
11122
+ - \`packages/shared/src/\``;
10623
11123
  function buildScoutPrompt(input) {
10624
11124
  const name = input.name ?? "\u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A";
10625
- const known = describeKnownProfile(input.profile ?? null);
10626
11125
  return [
10627
11126
  `\u0422\u044B \u2014 ${name}, \u0420\u0410\u0417\u0412\u0415\u0414\u0427\u0418\u041A \u0440\u043E\u044F: headless-\u0441\u0435\u0441\u0441\u0438\u044F (\u041D\u0415 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u0447\u0430\u0442), \u043E\u0434\u043D\u043E\u0440\u0430\u0437\u043E\u0432\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \xAB\u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\xBB.`,
10628
11127
  "",
10629
11128
  HEADLESS_BLOCK,
10630
11129
  "",
10631
- `\u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439: ${input.repoFullName}, \u043A\u043B\u043E\u043D \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: ${input.repoPath}. \u0422\u044B \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0432 \u043D\u0451\u043C.`,
11130
+ `\u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439: ${input.repoFullName}, \u0440\u0430\u0431\u043E\u0447\u0430\u044F \u043A\u043E\u043F\u0438\u044F \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: ${input.repoPath}. \u0422\u044B \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0432 \u043D\u0435\u0439.`,
10632
11131
  "",
10633
- "\u0427\u0422\u041E \u041E\u0422 \u0422\u0415\u0411\u042F \u041D\u0423\u0416\u041D\u041E. \u0420\u043E\u0439 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u043B\u0438 \u043A \u044D\u0442\u043E\u043C\u0443 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E, \u0438 \u0434\u0430\u043B\u044C\u0448\u0435 \u043E\u043D \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E: \u0437\u043E\u043D\u044B \u0434\u043B\u044F \u0437\u0430\u0434\u0430\u0447 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0440\u0430\u0441\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438, \u043A\u0430\u043A \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u2014 \u0434\u0440\u043E\u043D \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C \u2014 \u043D\u0435 \u0437\u043D\u0430\u0435\u0442 \u043D\u0438\u043A\u0442\u043E. \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0438 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0443: \u0442\u043E, \u0447\u0442\u043E \u0434\u0440\u043E\u043D \u043E\u0431\u044F\u0437\u0430\u043D \u0437\u043D\u0430\u0442\u044C \u0414\u041E \u043F\u0435\u0440\u0432\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438.",
11132
+ `\u0427\u0422\u041E \u041E\u0422 \u0422\u0415\u0411\u042F \u041D\u0423\u0416\u041D\u041E. \u0420\u043E\u0439 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u043B\u0438 \u043A \u044D\u0442\u043E\u043C\u0443 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E, \u0438 \u0434\u0430\u043B\u044C\u0448\u0435 \u043E\u043D \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E: \u043A\u0430\u043A \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u2014 \u0430\u0433\u0435\u043D\u0442 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C \u2014 \u043D\u0435 \u0437\u043D\u0430\u0435\u0442 \u043D\u0438\u043A\u0442\u043E, \u043A\u0430\u043A\u0438\u0435 \u043A\u0443\u0441\u043A\u0438 \u043A\u043E\u0434\u0430 \u043D\u0435\u043B\u044C\u0437\u044F \u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043E\u0434\u043D\u043E\u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u2014 \u0442\u043E\u0436\u0435. \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0438 \u043D\u0430\u043F\u0438\u0448\u0438 \u0444\u0430\u0439\u043B \`${input.briefPath}\`: \u0442\u043E, \u043E\u0431\u043E \u0447\u0442\u043E \u0430\u0433\u0435\u043D\u0442 \u0441\u043F\u043E\u0442\u044B\u043A\u0430\u0435\u0442\u0441\u044F \u0432 \u043D\u0435\u0437\u043D\u0430\u043A\u043E\u043C\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435.`,
10634
11133
  "",
10635
- "\u0421\u043E\u0431\u0435\u0440\u0438 \u0448\u0435\u0441\u0442\u044C \u043F\u043E\u043B\u0435\u0439:",
10636
- "1. `brief` \u2014 \u0447\u0442\u043E \u044D\u0442\u043E \u0437\u0430 \u043F\u0440\u043E\u0434\u0443\u043A\u0442 \u0438 \u043D\u0430 \u0447\u0451\u043C \u043D\u0430\u043F\u0438\u0441\u0430\u043D. \u041A\u043E\u0440\u043E\u0442\u043A\u043E, \u0447\u0442\u043E\u0431\u044B \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043D\u0435 \u0432\u044B\u0432\u043E\u0434\u0438\u043B \u044D\u0442\u043E \u0438\u0437 \u043A\u043E\u0434\u0430 \u0437\u0430\u043D\u043E\u0432\u043E.",
10637
- "2. `zones` \u2014 \u0437\u043E\u043D\u044B \u043A\u043E\u0434\u0430, \u043B\u0435\u0439\u0431\u043B\u044B `area:*`: \u043F\u0430\u043A\u0435\u0442\u044B, \u043C\u043E\u0434\u0443\u043B\u0438, \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438 \u0444\u0438\u0447. \u041F\u043E \u043D\u0438\u043C \u0440\u043E\u0439 \u0440\u0430\u0437\u0432\u043E\u0434\u0438\u0442 \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0443 \u043A\u0430\u0436\u0434\u043E\u0439 \u0437\u043E\u043D\u044B \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B `paths` (\u043A\u0430\u0442\u0430\u043B\u043E\u0433/\u043F\u0440\u0435\u0444\u0438\u043A\u0441, glob). \u0414\u0440\u043E\u0431\u0438 \u043F\u043E \u0420\u0415\u0410\u041B\u042C\u041D\u041E\u0419 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F, \u0430 \u043D\u0435 \u043F\u043E \u043A\u0440\u0430\u0441\u0438\u0432\u044B\u043C \u0441\u043B\u043E\u0432\u0430\u043C: \u0437\u043E\u043D\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043A\u0430\u0442\u0430\u043B\u043E\u0433, \u0431\u0435\u0441\u043F\u043E\u043B\u0435\u0437\u043D\u0430.",
10638
- "3. `env` \u2014 \u043A\u0430\u043A \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435: \u0447\u0435\u043C \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0431\u0430\u0437\u0430 (`database`), \u0447\u0435\u043C \u043D\u0430\u043A\u0430\u0442\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 (`migrations`), \u043A\u0430\u043A\u0438\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043D\u0443\u0436\u043D\u044B (`vars` \u2014 \u0422\u041E\u041B\u042C\u041A\u041E \u0418\u041C\u0415\u041D\u0410; \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u044D\u0442\u043E \u0441\u0435\u043A\u0440\u0435\u0442\u044B, \u043E\u043D\u0438 \u0443\u0435\u0434\u0443\u0442 \u0432 \u043F\u0440\u043E\u043C\u043F\u0442 \u0434\u0440\u043E\u043D\u0430 \u0438 \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043A\u0430\u043A \u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u0442\u0435\u043A\u0441\u0442). \u042D\u0442\u043E \u043F\u0440\u043E \u0442\u043E, \u0447\u0435\u043C \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E\u0442 \u0421\u0410\u041C\u041E \u041F\u0420\u0418\u041B\u041E\u0416\u0415\u041D\u0418\u0415, \u0430 \u043D\u0435 \u0442\u0435\u0441\u0442\u044B: \u0432\u043E \u043C\u043D\u043E\u0433\u0438\u0445 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\u0445 \u0442\u0435\u0441\u0442\u043E\u0432\u0430\u044F \u0431\u0430\u0437\u0430 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u0430\u044F \u2014 \u0441\u0432\u043E\u0439 compose-\u0444\u0430\u0439\u043B, \u0441\u0432\u043E\u0439 \u0441\u043A\u0440\u0438\u043F\u0442, \u0441\u0432\u043E\u0439 \u043F\u043E\u0440\u0442, \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u0432\u0438\u0434\u0430 `TEST_DATABASE_URL`. \u041E\u043D\u0430 \u0441\u044E\u0434\u0430 \u041D\u0415 \u0438\u0434\u0451\u0442. \u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438, \u043D\u0430\u043A\u0430\u0442\u0430\u043D\u043D\u044B\u0435 \u0432 \u0442\u0435\u0441\u0442\u043E\u0432\u0443\u044E \u0431\u0430\u0437\u0443, \u043E\u0441\u0442\u0430\u0432\u044F\u0442 \u0440\u0430\u0431\u043E\u0447\u0443\u044E \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u0439 \u0441\u0445\u0435\u043C\u0435, \u0430 \u0434\u0440\u043E\u043D \u0431\u0443\u0434\u0435\u0442 \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0435\u0451 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439. \u0418\u0449\u0438 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0432\u0445\u043E\u0434: \u0441\u043A\u0440\u0438\u043F\u0442 \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u0432 `scripts/`, `dev`/`start` \u0432 \u043C\u0430\u043D\u0438\u0444\u0435\u0441\u0442\u0435, \u0440\u0430\u0437\u0434\u0435\u043B README \xAB\u043A\u0430\u043A \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C\xBB, \u0444\u0430\u0439\u043B \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F \u0430\u0433\u0435\u043D\u0442\u043E\u0432.",
10639
- "4. `verify` \u2014 \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C (`commands`) \u0438 \u0447\u0442\u043E \u0437\u0434\u0435\u0441\u044C \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C (`green` \u2014 \u0441\u043B\u043E\u0432\u0430\u043C\u0438: \u0447\u0442\u043E \u0433\u043E\u043D\u044F\u0435\u0442\u0441\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, \u0447\u0442\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 CI, \u043A\u0430\u043A\u043E\u0439 \u0447\u0435\u043A \u0441\u043B\u0443\u0436\u0438\u0442 \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u043C \u043F\u0440\u0438\u0437\u043D\u0430\u043A\u043E\u043C \u0437\u0435\u043B\u0435\u043D\u0438).",
10640
- "5. `conventions` \u2014 \u044F\u0437\u044B\u043A \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432 (`commitLanguage`), \u043D\u0443\u0436\u0435\u043D \u043B\u0438 \u0441\u043B\u0430\u0433 \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438 (`slugInCommit`), \u0431\u0430\u0437\u043E\u0432\u0430\u044F \u0432\u0435\u0442\u043A\u0430 (`baseBranch`).",
10641
- "6. `spine` \u2014 \u041E\u0411\u0429\u0418\u0419 \u0425\u0420\u0415\u0411\u0415\u0422: \u0444\u0430\u0439\u043B\u044B \u0438 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043A\u0430\u0442\u044F\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C\u0438 PR \u0438 \u043D\u0435 \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044F\u0442\u0441\u044F (\u0441\u0445\u0435\u043C\u0430 \u0411\u0414, \u043E\u0431\u0449\u0438\u0435 \u0442\u0438\u043F\u044B, \u0440\u043E\u0443\u0442\u0438\u043D\u0433, \u043A\u043E\u043D\u0444\u0438\u0433 \u0441\u0431\u043E\u0440\u043A\u0438). \u0414\u0432\u0430 \u0434\u0440\u043E\u043D\u0430, \u043F\u0440\u0430\u0432\u044F\u0449\u0438\u0435 \u0442\u0430\u043A\u043E\u0435 \u043E\u0434\u043D\u043E\u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E, \u0433\u0430\u0440\u0430\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E \u0440\u0430\u0441\u0445\u043E\u0434\u044F\u0442\u0441\u044F \u0432 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0435.",
11134
+ "\u0427\u0442\u043E \u0432 \u043D\u0451\u043C \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C:",
11135
+ "1. **\u0427\u0442\u043E \u044D\u0442\u043E \u0437\u0430 \u043F\u0440\u043E\u0434\u0443\u043A\u0442 \u0438 \u043D\u0430 \u0447\u0451\u043C \u043D\u0430\u043F\u0438\u0441\u0430\u043D** \u2014 \u043A\u043E\u0440\u043E\u0442\u043A\u043E, \u0447\u0442\u043E\u0431\u044B \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0430\u0433\u0435\u043D\u0442 \u043D\u0435 \u0432\u044B\u0432\u043E\u0434\u0438\u043B \u044D\u0442\u043E \u0438\u0437 \u043A\u043E\u0434\u0430 \u0437\u0430\u043D\u043E\u0432\u043E.",
11136
+ "2. **\u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435** \u2014 \u0447\u0435\u043C \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0431\u0430\u0437\u0430, \u0447\u0435\u043C \u043D\u0430\u043A\u0430\u0442\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438, \u043A\u0430\u043A\u0438\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043D\u0443\u0436\u043D\u044B (\u0422\u041E\u041B\u042C\u041A\u041E \u0418\u041C\u0415\u041D\u0410; \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u044D\u0442\u043E \u0441\u0435\u043A\u0440\u0435\u0442\u044B, \u0430 \u0444\u0430\u0439\u043B \u0443\u0435\u0434\u0435\u0442 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043A\u0430\u043A \u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u0442\u0435\u043A\u0441\u0442). \u042D\u0442\u043E \u043F\u0440\u043E \u0442\u043E, \u0447\u0435\u043C \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E\u0442 \u0421\u0410\u041C\u041E \u041F\u0420\u0418\u041B\u041E\u0416\u0415\u041D\u0418\u0415, \u0430 \u043D\u0435 \u0442\u0435\u0441\u0442\u044B: \u0432\u043E \u043C\u043D\u043E\u0433\u0438\u0445 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\u0445 \u0442\u0435\u0441\u0442\u043E\u0432\u0430\u044F \u0431\u0430\u0437\u0430 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u0430\u044F \u2014 \u0441\u0432\u043E\u0439 compose-\u0444\u0430\u0439\u043B, \u0441\u0432\u043E\u0439 \u0441\u043A\u0440\u0438\u043F\u0442, \u0441\u0432\u043E\u0439 \u043F\u043E\u0440\u0442, \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u0432\u0438\u0434\u0430 `TEST_DATABASE_URL`. \u041E\u043D\u0430 \u0441\u044E\u0434\u0430 \u041D\u0415 \u0438\u0434\u0451\u0442: \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438, \u043D\u0430\u043A\u0430\u0442\u0430\u043D\u043D\u044B\u0435 \u0432 \u0442\u0435\u0441\u0442\u043E\u0432\u0443\u044E \u0431\u0430\u0437\u0443, \u043E\u0441\u0442\u0430\u0432\u044F\u0442 \u0440\u0430\u0431\u043E\u0447\u0443\u044E \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u0439 \u0441\u0445\u0435\u043C\u0435, \u0430 \u0430\u0433\u0435\u043D\u0442 \u0431\u0443\u0434\u0435\u0442 \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0435\u0451 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439. \u0418\u0449\u0438 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0432\u0445\u043E\u0434: \u0441\u043A\u0440\u0438\u043F\u0442 \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u0432 `scripts/`, `dev`/`start` \u0432 \u043C\u0430\u043D\u0438\u0444\u0435\u0441\u0442\u0435, \u0440\u0430\u0437\u0434\u0435\u043B README \xAB\u043A\u0430\u043A \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C\xBB.",
11137
+ "3. **\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438** \u2014 \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0438 \u0447\u0442\u043E \u0437\u0434\u0435\u0441\u044C \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C (\u0441\u043B\u043E\u0432\u0430\u043C\u0438: \u0447\u0442\u043E \u0433\u043E\u043D\u044F\u0435\u0442\u0441\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, \u0447\u0442\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 CI, \u043A\u0430\u043A\u043E\u0439 \u0447\u0435\u043A \u0441\u043B\u0443\u0436\u0438\u0442 \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u043C \u043F\u0440\u0438\u0437\u043D\u0430\u043A\u043E\u043C \u0437\u0435\u043B\u0435\u043D\u0438).",
11138
+ "4. **\u0421\u043E\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u044F** \u2014 \u044F\u0437\u044B\u043A \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432, \u043D\u0443\u0436\u0435\u043D \u043B\u0438 \u0441\u043B\u0430\u0433 \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438, \u0431\u0430\u0437\u043E\u0432\u0430\u044F \u0432\u0435\u0442\u043A\u0430.",
11139
+ "5. **\u0417\u043E\u043D\u044B \u043A\u043E\u0434\u0430** \u2014 \u043F\u0430\u043A\u0435\u0442\u044B, \u043C\u043E\u0434\u0443\u043B\u0438, \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438 \u0444\u0438\u0447 \u0421 \u041F\u0423\u0422\u042F\u041C\u0418. \u041F\u043E \u043D\u0438\u043C \u0440\u043E\u0439 \u0440\u0430\u0437\u0432\u043E\u0434\u0438\u0442 \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0437\u043E\u043D\u0430 \u0431\u0435\u0437 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430 \u0431\u0435\u0441\u043F\u043E\u043B\u0435\u0437\u043D\u0430; \u0434\u0440\u043E\u0431\u0438 \u043F\u043E \u0420\u0415\u0410\u041B\u042C\u041D\u041E\u0419 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F, \u0430 \u043D\u0435 \u043F\u043E \u043A\u0440\u0430\u0441\u0438\u0432\u044B\u043C \u0441\u043B\u043E\u0432\u0430\u043C.",
11140
+ "6. **\u041E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442** \u2014 \u0444\u0430\u0439\u043B\u044B \u0438 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043A\u0430\u0442\u044F\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C\u0438 PR \u0438 \u043D\u0435 \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044F\u0442\u0441\u044F (\u0441\u0445\u0435\u043C\u0430 \u0411\u0414, \u043E\u0431\u0449\u0438\u0435 \u0442\u0438\u043F\u044B, \u0440\u043E\u0443\u0442\u0438\u043D\u0433, \u043A\u043E\u043D\u0444\u0438\u0433 \u0441\u0431\u043E\u0440\u043A\u0438). \u0414\u0432\u0430 \u0430\u0433\u0435\u043D\u0442\u0430, \u043F\u0440\u0430\u0432\u044F\u0449\u0438\u0435 \u0442\u0430\u043A\u043E\u0435 \u043E\u0434\u043D\u043E\u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E, \u0433\u0430\u0440\u0430\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E \u0440\u0430\u0441\u0445\u043E\u0434\u044F\u0442\u0441\u044F \u0432 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0435.",
10642
11141
  "",
10643
11142
  "\u0413\u0414\u0415 \u0421\u041C\u041E\u0422\u0420\u0415\u0422\u042C (\u043D\u0435 \u0447\u0438\u0442\u0430\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u2014 \u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430 \u043F\u043E\u0442\u043E\u043B\u043A\u043E\u043C \u0442\u0440\u0430\u0442): README \u0438 \u0444\u0430\u0439\u043B\u044B \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F \u0430\u0433\u0435\u043D\u0442\u043E\u0432 (CLAUDE.md, AGENTS.md, .cursorrules, \u043A\u0430\u0442\u0430\u043B\u043E\u0433 .claude/skills), \u043C\u0430\u043D\u0438\u0444\u0435\u0441\u0442 \u043F\u0430\u043A\u0435\u0442\u043D\u043E\u0433\u043E \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\u0430 \u0438 \u0435\u0433\u043E scripts, \u043A\u043E\u043D\u0444\u0438\u0433 \u043C\u043E\u043D\u043E\u0440\u0435\u043F\u043E (workspaces/turbo/nx/lerna), docker-compose \u0438 Makefile, .env.example (\u0418\u041C\u0415\u041D\u0410 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445), \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0439, CI-\u0432\u043E\u0440\u043A\u0444\u043B\u043E\u0443 (.github/workflows \u0438 \u0430\u043D\u0430\u043B\u043E\u0433\u0438), \u0432\u0435\u0440\u0445\u043D\u0438\u0439 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u043E\u0432 \u043A\u043E\u0434\u0430, `git log --oneline -30` (\u044F\u0437\u044B\u043A \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432 \u0438 \u0435\u0441\u0442\u044C \u043B\u0438 \u0432 \u043D\u0438\u0445 \u0441\u043B\u0430\u0433 \u0437\u0430\u0434\u0430\u0447\u0438) \u0438 `git symbolic-ref refs/remotes/origin/HEAD` (\u0431\u0430\u0437\u043E\u0432\u0430\u044F \u0432\u0435\u0442\u043A\u0430).",
10644
11143
  "",
10645
11144
  "\u0427\u0415\u0413\u041E \u041D\u0415 \u0414\u0415\u041B\u0410\u0422\u042C:",
10646
- "\u2022 \u041D\u0418\u0427\u0415\u0413\u041E \u041D\u0415 \u041F\u0420\u0410\u0412\u042C \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438: \u043D\u0438 \u0444\u0430\u0439\u043B\u043E\u0432, \u043D\u0438 \u0432\u0435\u0442\u043E\u043A, \u043D\u0438 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432. \u0422\u044B \u0447\u0438\u0442\u0430\u0435\u0448\u044C, \u0430 \u043D\u0435 \u0447\u0438\u043D\u0438\u0448\u044C. \u041D\u0435\u0442 CI \u0438\u043B\u0438 \u0442\u0435\u0441\u0442\u043E\u0432 \u2014 \u0442\u0430\u043A \u0438 \u0437\u0430\u043F\u0438\u0448\u0438, \u044D\u0442\u043E \u0444\u0430\u043A\u0442 \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, \u0430 \u043D\u0435 \u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0435\u0431\u0435.",
10647
- "\u2022 \u041D\u0415 \u0412\u042B\u0414\u0423\u041C\u042B\u0412\u0410\u0419 \u043A\u043E\u043C\u0430\u043D\u0434\u044B. \u041A\u0430\u0436\u0434\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u0442\u044B \u043F\u0438\u0448\u0435\u0448\u044C, \u0434\u043E\u043B\u0436\u043D\u0430 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u0442\u044C\u0441\u044F \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0426\u0415\u041B\u0418\u041A\u041E\u041C \u2014 \u0441\u0442\u0440\u043E\u043A\u043E\u0439, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043C\u043E\u0436\u043D\u043E \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C (scripts, \u043C\u0430\u043D\u0438\u0444\u0435\u0441\u0442 \u043F\u0430\u043A\u0435\u0442\u0430, compose, Makefile, \u0432\u043E\u0440\u043A\u0444\u043B\u043E\u0443, README) \u2014 \u043F\u043E \u044D\u0442\u0438\u043C \u043A\u043E\u043C\u0430\u043D\u0434\u0430\u043C \u0434\u0440\u043E\u043D \u0431\u0443\u0434\u0435\u0442 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C \u0447\u0443\u0436\u0443\u044E \u0431\u0430\u0437\u0443. \u041D\u0435 \u043D\u0430\u0448\u0451\u043B \u2014 \u043F\u043E\u043B\u0435 \u043D\u0435 \u043F\u0440\u0438\u0441\u044B\u043B\u0430\u0439.",
10648
- "\u2022 \u041D\u0415 \u0421\u041A\u041B\u0415\u0418\u0412\u0410\u0419 \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0438\u0437 \u043A\u0443\u0441\u043A\u043E\u0432. \u0414\u0432\u0435 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0438\u0435 \u0441\u0442\u0440\u043E\u043A\u0438, \u0441\u043E\u0435\u0434\u0438\u043D\u0451\u043D\u043D\u044B\u0435 \u0442\u043E\u0431\u043E\u0439 \u0432 \u043E\u0434\u043D\u0443 (\u043F\u043E\u0434\u0441\u0442\u0430\u0432\u0438\u043B URL \u0438\u0437 \u043E\u0434\u043D\u043E\u0433\u043E \u0441\u043A\u0440\u0438\u043F\u0442\u0430 \u0432 \u0432\u044B\u0437\u043E\u0432 \u0438\u0437 \u0434\u0440\u0443\u0433\u043E\u0433\u043E, \u0434\u043E\u043F\u0438\u0441\u0430\u043B \u0444\u043B\u0430\u0433, \u043E\u0431\u0435\u0440\u043D\u0443\u043B \u0432 \u043F\u0440\u0438\u0441\u0432\u0430\u0438\u0432\u0430\u043D\u0438\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439), \u0434\u0430\u044E\u0442 \u043A\u043E\u043C\u0430\u043D\u0434\u0443, \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435\u0442 \u0438 \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043D\u0438\u043A\u0442\u043E \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B. \u0412\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u043E\u043D\u0430 \u043F\u0440\u0438 \u044D\u0442\u043E\u043C \u043A\u0430\u043A \u0444\u0430\u043A\u0442. \u041D\u0435 \u0443\u0432\u0435\u0440\u0435\u043D, \u0447\u0442\u043E \u0441\u0442\u0440\u043E\u043A\u0430 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u0440\u043E\u0432\u043D\u043E \u0432 \u0442\u0430\u043A\u043E\u043C \u0432\u0438\u0434\u0435, \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u044B\u043B\u0430\u0439 \u0435\u0451.",
10649
- "\u2022 \u041F\u043E\u043B\u0435, \u043F\u0440\u043E \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0441\u043A\u0430\u0437\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E, \u041F\u0420\u041E\u041F\u0423\u0421\u0422\u0418 \u0446\u0435\u043B\u0438\u043A\u043E\u043C. \u041F\u0440\u043E\u043F\u0443\u0441\u043A \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u0442\u0438\u0440\u0430\u0435\u0442; \u0432\u044B\u0434\u0443\u043C\u0430\u043D\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u2014 \u0441\u0442\u0438\u0440\u0430\u0435\u0442 \u043F\u0440\u0430\u0432\u0434\u0443.",
11145
+ `\u2022 \u041D\u0415 \u041F\u0420\u0410\u0412\u042C \u041D\u0418\u0427\u0415\u0413\u041E, \u041A\u0420\u041E\u041C\u0415 \`${input.briefPath}\`. \u041D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0434\u0440\u0443\u0433\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0430, \u043D\u0438 \u0432\u0435\u0442\u043E\u043A, \u043D\u0438 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432, \u043D\u0438 PR \u2014 \u043A\u043E\u043C\u043C\u0438\u0442 \u0438 PR \u0441\u0434\u0435\u043B\u0430\u0435\u0442 \u0440\u043E\u0439 \u0441\u0430\u043C. CLAUDE.md \u041D\u0415 \u0422\u0420\u041E\u0413\u0410\u0419 \u0412\u041E\u0412\u0421\u0415: \u0441\u0442\u0440\u043E\u043A\u0443 \u0438\u043C\u043F\u043E\u0440\u0442\u0430 \u0442\u0443\u0434\u0430 \u0434\u043E\u043F\u0438\u0448\u0435\u0442 \u0440\u043E\u0439, \u044D\u0442\u043E \u043D\u0435 \u0442\u0432\u043E\u044F \u0440\u0430\u0431\u043E\u0442\u0430. \u041D\u0435\u0442 CI \u0438\u043B\u0438 \u0442\u0435\u0441\u0442\u043E\u0432 \u2014 \u0442\u0430\u043A \u0438 \u0437\u0430\u043F\u0438\u0448\u0438, \u044D\u0442\u043E \u0444\u0430\u043A\u0442 \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, \u0430 \u043D\u0435 \u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0435\u0431\u0435.`,
11146
+ "\u2022 \u041D\u0415 \u0412\u042B\u0414\u0423\u041C\u042B\u0412\u0410\u0419 \u043A\u043E\u043C\u0430\u043D\u0434\u044B. \u041A\u0430\u0436\u0434\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u0442\u044B \u043F\u0438\u0448\u0435\u0448\u044C, \u0434\u043E\u043B\u0436\u043D\u0430 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u0442\u044C\u0441\u044F \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0426\u0415\u041B\u0418\u041A\u041E\u041C \u2014 \u0441\u0442\u0440\u043E\u043A\u043E\u0439, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043C\u043E\u0436\u043D\u043E \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C (scripts, \u043C\u0430\u043D\u0438\u0444\u0435\u0441\u0442 \u043F\u0430\u043A\u0435\u0442\u0430, compose, Makefile, \u0432\u043E\u0440\u043A\u0444\u043B\u043E\u0443, README) \u2014 \u043F\u043E \u044D\u0442\u0438\u043C \u043A\u043E\u043C\u0430\u043D\u0434\u0430\u043C \u0430\u0433\u0435\u043D\u0442 \u0431\u0443\u0434\u0435\u0442 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C \u0447\u0443\u0436\u0443\u044E \u0431\u0430\u0437\u0443. \u041D\u0435 \u043D\u0430\u0448\u0451\u043B \u2014 \u0442\u0430\u043A \u0438 \u043D\u0430\u043F\u0438\u0448\u0438, \u0447\u0442\u043E \u043D\u0435 \u043D\u0430\u0448\u0451\u043B.",
11147
+ "\u2022 \u041D\u0415 \u0421\u041A\u041B\u0415\u0418\u0412\u0410\u0419 \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0438\u0437 \u043A\u0443\u0441\u043A\u043E\u0432. \u0414\u0432\u0435 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0438\u0435 \u0441\u0442\u0440\u043E\u043A\u0438, \u0441\u043E\u0435\u0434\u0438\u043D\u0451\u043D\u043D\u044B\u0435 \u0442\u043E\u0431\u043E\u0439 \u0432 \u043E\u0434\u043D\u0443 (\u043F\u043E\u0434\u0441\u0442\u0430\u0432\u0438\u043B URL \u0438\u0437 \u043E\u0434\u043D\u043E\u0433\u043E \u0441\u043A\u0440\u0438\u043F\u0442\u0430 \u0432 \u0432\u044B\u0437\u043E\u0432 \u0438\u0437 \u0434\u0440\u0443\u0433\u043E\u0433\u043E, \u0434\u043E\u043F\u0438\u0441\u0430\u043B \u0444\u043B\u0430\u0433, \u043E\u0431\u0435\u0440\u043D\u0443\u043B \u0432 \u043F\u0440\u0438\u0441\u0432\u0430\u0438\u0432\u0430\u043D\u0438\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439), \u0434\u0430\u044E\u0442 \u043A\u043E\u043C\u0430\u043D\u0434\u0443, \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435\u0442 \u0438 \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043D\u0438\u043A\u0442\u043E \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B. \u0412\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u043E\u043D\u0430 \u043F\u0440\u0438 \u044D\u0442\u043E\u043C \u043A\u0430\u043A \u0444\u0430\u043A\u0442.",
11148
+ "\u2022 \u0420\u0430\u0437\u0434\u0435\u043B, \u043F\u0440\u043E \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0441\u043A\u0430\u0437\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E, \u041F\u0420\u041E\u041F\u0423\u0421\u0422\u0418 \u0446\u0435\u043B\u0438\u043A\u043E\u043C. \u041F\u0440\u043E\u043F\u0443\u0441\u043A \u0447\u0435\u0441\u0442\u0435\u043D; \u0432\u044B\u0434\u0443\u043C\u0430\u043D\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u2014 \u043D\u0435\u0442.",
10650
11149
  "\u2022 \u041D\u0438\u043A\u0430\u043A\u0438\u0445 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432, \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0438 \u043F\u0430\u0440\u043E\u043B\u0435\u0439 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u043C\u0435\u043D\u0430 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445.",
10651
11150
  "\u2022 \u041A\u043E\u043C\u0430\u043D\u0434\u044B \u043F\u043E\u0434\u043D\u044F\u0442\u0438\u044F \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u041D\u0415 \u0417\u0410\u041F\u0423\u0421\u041A\u0410\u0419 (\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043D\u0430 \u0436\u0438\u0432\u043E\u0439 \u0431\u0430\u0437\u0435 \u2014 \u043D\u0435 \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0430).",
10652
11151
  "",
10653
- known,
11152
+ describeKnownBrief(input.brief ?? null, input.briefPath),
11153
+ "",
11154
+ `\u0424\u0410\u0419\u041B \u0410\u0413\u0415\u041D\u0422-\u041D\u0415\u0419\u0422\u0420\u0410\u041B\u042C\u041D\u042B\u0419: \u043E\u0431\u044B\u0447\u043D\u044B\u0439 markdown \u043F\u0440\u043E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u0419, \u0431\u0435\u0437 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439 \u043A \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E\u043C\u0443 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044E \u0438 \u0431\u0435\u0437 claude-\u0441\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0438. \u0415\u0433\u043E \u0447\u0438\u0442\u0430\u044E\u0442 \u0440\u0430\u0437\u043D\u044B\u0435 \u0430\u0433\u0435\u043D\u0442\u044B, \u043A\u0430\u0436\u0434\u044B\u0439 \u0441\u0432\u043E\u0438\u043C \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C.`,
10654
11155
  "",
10655
- `\u041A\u0410\u041A \u0421\u0414\u0410\u0422\u042C. \u0417\u0430\u043F\u0438\u0448\u0438 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u041E\u0414\u041D\u0418\u041C JSON-\u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \u0432 \u0444\u0430\u0439\u043B \`${input.outFile}\` (\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0438\u0448\u0438, \u0435\u0441\u043B\u0438 \u043E\u043D \u0435\u0441\u0442\u044C) \u2014 \u0438 \u043D\u0430 \u044D\u0442\u043E\u043C \u0437\u0430\u043A\u043E\u043D\u0447\u0438: \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0435 \u0432\u044B\u0432\u043E\u0434\u0438 \u0432 stdout \u043D\u0438\u0447\u0435\u0433\u043E, \u043A\u0440\u043E\u043C\u0435 \u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u0438 \xAB\u0433\u043E\u0442\u043E\u0432\u043E\xBB. \u0424\u0430\u0439\u043B \u0447\u0438\u0442\u0430\u0435\u0442 \u0434\u0435\u043C\u043E\u043D \u0440\u043E\u044F; \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0443\u0432\u0438\u0434\u0438\u0442 \u0442\u0432\u043E\u0451 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043D\u0430 \u044D\u043A\u0440\u0430\u043D\u0435 \u043F\u0440\u0438\u0451\u043C\u043A\u0438 \u0438 \u0440\u0435\u0448\u0438\u0442, \u0447\u0442\u043E \u043F\u0440\u0438\u043D\u044F\u0442\u044C.`,
11156
+ `\u041A\u0410\u041A \u0421\u0414\u0410\u0422\u042C. \u0417\u0430\u043F\u0438\u0448\u0438 \u0444\u0430\u0439\u043B \`${input.briefPath}\` (\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0438\u0448\u0438, \u0435\u0441\u043B\u0438 \u043E\u043D \u0435\u0441\u0442\u044C) \u2014 \u0438 \u043D\u0430 \u044D\u0442\u043E\u043C \u0437\u0430\u043A\u043E\u043D\u0447\u0438: \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0435 \u0432\u044B\u0432\u043E\u0434\u0438 \u0432 stdout \u043D\u0438\u0447\u0435\u0433\u043E, \u043A\u0440\u043E\u043C\u0435 \u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u0438 \xAB\u0433\u043E\u0442\u043E\u0432\u043E\xBB.`,
10656
11157
  "",
10657
- "\u0424\u043E\u0440\u043C\u0430 \u043E\u0442\u0432\u0435\u0442\u0430 (\u043B\u0438\u0448\u043D\u0438\u0435 \u043A\u043B\u044E\u0447\u0438 \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0439, \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u043F\u043E\u043B\u044F \u043F\u0440\u043E\u0441\u0442\u043E \u043D\u0435 \u043F\u0438\u0448\u0438):",
10658
- "```json",
11158
+ "\u0421\u043A\u0435\u043B\u0435\u0442 (\u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0438 \u0434\u0435\u0440\u0436\u0438 \u044D\u0442\u0438, \u043F\u0443\u0441\u0442\u044B\u0435 \u0440\u0430\u0437\u0434\u0435\u043B\u044B \u043D\u0435 \u043F\u0438\u0448\u0438):",
11159
+ "```markdown",
10659
11160
  SHAPE,
10660
11161
  "```",
10661
11162
  "",
10662
- "\u0422\u0412\u041E\u0401 \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0415\u041D\u0418\u0415 \u041D\u0415 \u041F\u0420\u0418\u041C\u0415\u041D\u042F\u0415\u0422\u0421\u042F \u041C\u041E\u041B\u0427\u0410: \u0435\u0433\u043E \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A, \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u043F\u043E\u043B\u044E. \u041F\u043E\u044D\u0442\u043E\u043C\u0443 \u043D\u0435 \u0437\u0430\u043D\u0438\u0436\u0430\u0439 \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0441\u0442\u044C \u0438\u0437 \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u043E\u0441\u0442\u0438 \u2014 \u043D\u043E \u0438 \u043D\u0435 \u0432\u044B\u0434\u0430\u0432\u0430\u0439 \u0434\u043E\u0433\u0430\u0434\u043A\u0443 \u0437\u0430 \u043D\u0430\u0431\u043B\u044E\u0434\u0435\u043D\u0438\u0435: \u0442\u043E, \u0432 \u0447\u0451\u043C \u043D\u0435 \u0443\u0432\u0435\u0440\u0435\u043D, \u043B\u0443\u0447\u0448\u0435 \u043E\u0431\u044A\u044F\u0441\u043D\u0438 \u0441\u043B\u043E\u0432\u0430\u043C\u0438 \u0432 `green`/`about`, \u0447\u0435\u043C \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439."
11163
+ "\u0422\u0412\u041E\u0419 \u0424\u0410\u0419\u041B \u0423\u0412\u0418\u0414\u0418\u0422 \u0427\u0415\u041B\u041E\u0412\u0415\u041A \u0412 PR, \u043D\u043E \u0438\u0441\u043F\u043E\u043B\u043D\u044F\u0442\u044C \u043F\u043E \u043D\u0435\u043C\u0443 \u0431\u0443\u0434\u0443\u0442 \u0430\u0433\u0435\u043D\u0442\u044B. \u041F\u043E\u044D\u0442\u043E\u043C\u0443 \u043F\u0438\u0448\u0438 \u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u043E: \u043A\u043E\u043C\u0430\u043D\u0434\u0430, \u043F\u0443\u0442\u044C, \u0438\u043C\u044F \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439. \u0422\u043E, \u0432 \u0447\u0451\u043C \u043D\u0435 \u0443\u0432\u0435\u0440\u0435\u043D, \u043E\u0431\u044A\u044F\u0441\u043D\u0438 \u0441\u043B\u043E\u0432\u0430\u043C\u0438 \u2014 \u044D\u0442\u043E \u0447\u0435\u0441\u0442\u043D\u0435\u0435, \u0447\u0435\u043C \u0443\u0433\u0430\u0434\u0430\u043D\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043A\u0442\u043E-\u0442\u043E \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442."
10663
11164
  ].join("\n");
10664
11165
  }
10665
- function describeKnownProfile(profile) {
10666
- if (!profile) {
10667
- return "\u0420\u0410\u0417\u041C\u0415\u0422\u041A\u0418 \u0423 \u042D\u0422\u041E\u0413\u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F \u0415\u0429\u0401 \u041D\u0415\u0422 \u2014 \u0442\u044B \u0440\u0430\u0437\u043C\u0435\u0447\u0430\u0435\u0448\u044C \u0435\u0433\u043E \u043F\u0435\u0440\u0432\u044B\u043C. \u041F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0439 \u0432\u0441\u0435 \u0448\u0435\u0441\u0442\u044C \u043F\u043E\u043B\u0435\u0439, \u043A\u0430\u043A\u0438\u0435 \u0441\u043C\u043E\u0436\u0435\u0448\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u043A\u043E\u0434\u043E\u043C.";
11166
+ function describeKnownBrief(brief, briefPath = BRIEF_PATH) {
11167
+ if (!brief?.trim()) {
11168
+ return `\u0411\u0420\u0418\u0424\u0410 \u0423 \u042D\u0422\u041E\u0413\u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F \u0415\u0429\u0401 \u041D\u0415\u0422 \u2014 \u0442\u044B \u043F\u0438\u0448\u0435\u0448\u044C \u0435\u0433\u043E \u043F\u0435\u0440\u0432\u044B\u043C. \u0417\u0430\u043F\u043E\u043B\u043D\u0438 \u0432\u0441\u0435 \u0440\u0430\u0437\u0434\u0435\u043B\u044B, \u043A\u0430\u043A\u0438\u0435 \u0441\u043C\u043E\u0436\u0435\u0448\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u043A\u043E\u0434\u043E\u043C.`;
10668
11169
  }
10669
- const filled = SCOUT_FIELDS.filter((f) => profile[f] !== void 0 && profile[f] !== null);
10670
- const byHuman = filled.filter((f) => profile.sources?.[f] === "human");
10671
- const lines = [
10672
- "\u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u0419 \u0423\u0416\u0415 \u0420\u0410\u0417\u041C\u0415\u0427\u0415\u041D \u2014 \u044D\u0442\u043E \u041F\u041E\u0412\u0422\u041E\u0420\u041D\u0410\u042F \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0430. \u0427\u0442\u043E \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0441\u0435\u0439\u0447\u0430\u0441:",
10673
- "```json",
10674
- JSON.stringify(pickFields(profile), null, 2),
11170
+ return [
11171
+ `\u042D\u0422\u041E \u041F\u041E\u0412\u0422\u041E\u0420\u041D\u042B\u0419 \u041F\u0420\u041E\u0425\u041E\u0414 \u2014 \`${briefPath}\` \u0443\u0436\u0435 \u0435\u0441\u0442\u044C, \u0438 \u0432\u043E\u0442 \u043E\u043D \u0446\u0435\u043B\u0438\u043A\u043E\u043C:`,
11172
+ "```markdown",
11173
+ brief.trim(),
10675
11174
  "```",
10676
- "\u0422\u0432\u043E\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0435\u043F\u0435\u0440\u044C \u2014 \u0420\u0410\u0417\u041D\u0418\u0426\u0410, \u0430 \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437: \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0436\u0438\u0432\u0451\u0442, \u043F\u0430\u043A\u0435\u0442\u044B \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F, \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043C\u0435\u043D\u044F\u044E\u0442\u0441\u044F. \u041F\u043E\u043B\u0435, \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u043F\u043E-\u043F\u0440\u0435\u0436\u043D\u0435\u043C\u0443 \u0432\u0435\u0440\u043D\u043E, \u041D\u0415 \u041F\u0420\u0418\u0421\u042B\u041B\u0410\u0419 \u0412\u041E\u0412\u0421\u0415 \u2014 \u043F\u0440\u043E\u043F\u0443\u0441\u043A \u0438 \u0435\u0441\u0442\u044C \xAB\u0442\u0443\u0442 \u0431\u0435\u0437 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439\xBB.",
10677
- "\xAB\u041F\u0440\u0438\u0441\u043B\u0430\u0442\u044C \u043A\u0430\u043A \u0435\u0441\u0442\u044C\xBB \u2014 \u041D\u0415 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435, \u0438 \u044D\u0442\u043E \u0433\u043B\u0430\u0432\u043D\u0430\u044F \u043B\u043E\u0432\u0443\u0448\u043A\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E\u0439 \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0438: \u0437\u0430\u043D\u043E\u0432\u043E \u0442\u044B \u043D\u0430\u043F\u0438\u0448\u0435\u0448\u044C \u0434\u0440\u0443\u0433\u0438\u043C\u0438 \u0441\u043B\u043E\u0432\u0430\u043C\u0438, \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u0435\u0435 \u0438 \u0434\u043B\u0438\u043D\u043D\u0435\u0435, \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043F\u043E\u043B\u0443\u0447\u0438\u0442 \u044D\u0442\u043E \u043A\u0430\u043A \xAB\u0438\u0437\u043C\u0435\u043D\u0438\u043B\u043E\u0441\u044C\xBB, \u0438 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0430\u044F \u043F\u0440\u0430\u0432\u043A\u0430 \u0443\u0442\u043E\u043D\u0435\u0442 \u0441\u0440\u0435\u0434\u0438 \u043F\u0435\u0440\u0435\u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043E\u043A. \u041F\u0440\u0438\u0441\u044B\u043B\u0430\u0439 \u043F\u043E\u043B\u0435, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043A\u043E\u0434 \u043F\u0440\u044F\u043C\u043E \u041F\u0420\u041E\u0422\u0418\u0412\u041E\u0420\u0415\u0427\u0418\u0422 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u043C\u0443: \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442, \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D, \u043F\u043E\u044F\u0432\u0438\u043B\u0441\u044F \u043F\u0430\u043A\u0435\u0442, \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0439 \u0437\u0435\u043B\u0435\u043D\u0438. \u0421\u0442\u0430\u043B\u043E \u043A\u0440\u0430\u0441\u0438\u0432\u0435\u0435 \u2014 \u043D\u0435 \u043F\u043E\u0432\u043E\u0434.",
10678
- "\u041F\u0440\u0438\u0441\u044B\u043B\u0430\u0435\u0448\u044C \u2014 \u043F\u0440\u0438\u0441\u044B\u043B\u0430\u0439 \u043F\u043E\u043B\u0435 \u0426\u0415\u041B\u0418\u041A\u041E\u041C (\u043E\u043D\u043E \u0437\u0430\u043C\u0435\u043D\u044F\u0435\u0442 \u043F\u0440\u0435\u0436\u043D\u0435\u0435), \u043D\u043E \u0440\u043E\u0432\u043D\u043E \u0441 \u0442\u043E\u0439 \u043F\u0440\u0430\u0432\u043A\u043E\u0439, \u0440\u0430\u0434\u0438 \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u043F\u0440\u0438\u0441\u043B\u0430\u043B: \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u043E\u0435 \u0432\u043D\u0443\u0442\u0440\u0438 \u043D\u0435\u0433\u043E \u043E\u0441\u0442\u0430\u0432\u044C \u0414\u041E\u0421\u041B\u041E\u0412\u041D\u041E \u0442\u0430\u043A, \u043A\u0430\u043A \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0441\u0435\u0439\u0447\u0430\u0441, \u0432\u043F\u043B\u043E\u0442\u044C \u0434\u043E \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043E\u043A. \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043E\u0434\u043D\u0443 \u0437\u043E\u043D\u0443 \u2014 \u044D\u0442\u043E \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u0432 \u043D\u0435\u0433\u043E \u0441\u0442\u0440\u043E\u043A\u0443, \u0430 \u043D\u0435 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u0430\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u0437\u0430\u043D\u043E\u0432\u043E."
10679
- ];
10680
- if (byHuman.length) {
10681
- lines.push(
10682
- `\u26A0\uFE0F \u042D\u0422\u0418 \u041F\u041E\u041B\u042F \u0412\u041F\u0418\u0421\u0410\u041B \u0427\u0415\u041B\u041E\u0412\u0415\u041A: ${byHuman.join(", ")}. \u041E\u043D \u0437\u043D\u0430\u0435\u0442 \u043F\u0440\u043E \u043F\u0440\u043E\u0435\u043A\u0442 \u0442\u043E, \u0447\u0435\u0433\u043E \u0432 \u043A\u043E\u0434\u0435 \u043D\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043E, \u2014 \u043D\u0435 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u0438\u0445 \xAB\u0434\u043B\u044F \u043F\u043E\u0440\u044F\u0434\u043A\u0430\xBB \u0438 \u043D\u0435 \u043F\u0440\u0438\u0432\u043E\u0434\u0438 \u043A \u0441\u0432\u043E\u0435\u043C\u0443 \u0441\u0442\u0438\u043B\u044E. \u041C\u0435\u043D\u044F\u0439, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043A\u043E\u0434 \u043F\u0440\u044F\u043C\u043E \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u043C\u0443 (\u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442, \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D), \u0438 \u0442\u043E\u0433\u0434\u0430 \u043E\u0431\u044A\u044F\u0441\u043D\u0438 \u0440\u0430\u0441\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0435 \u0432 \u0442\u0435\u043A\u0441\u0442\u0435 \u043F\u043E\u043B\u044F: \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0443\u0432\u0438\u0434\u0438\u0442 \u0435\u0433\u043E \u043D\u0430 \u043F\u0440\u0438\u0451\u043C\u043A\u0435 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0439 \u043F\u043E\u043C\u0435\u0442\u043A\u043E\u0439 \u0438 \u0440\u0435\u0448\u0438\u0442 \u0441\u0430\u043C.`
10683
- );
10684
- }
10685
- return lines.join("\n");
10686
- }
10687
- function pickFields(profile) {
10688
- const out = {};
10689
- for (const f of SCOUT_FIELDS) if (profile[f] !== void 0 && profile[f] !== null) out[f] = profile[f];
10690
- return out;
11175
+ "\u0424\u0430\u0439\u043B \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\u0438\u0442 \u0442\u0435\u0431\u0435 \u0446\u0435\u043B\u0438\u043A\u043E\u043C: \u0442\u044B \u0435\u0433\u043E \u041F\u0415\u0420\u0415\u0417\u0410\u041F\u0418\u0421\u042B\u0412\u0410\u0415\u0428\u042C, \u0430 \u043D\u0435 \u0434\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0448\u044C. \u041F\u043E\u044D\u0442\u043E\u043C\u0443 \u043F\u0438\u0448\u0438 \u0435\u0433\u043E \u0437\u0430\u043D\u043E\u0432\u043E \u2014 \u043D\u043E \u043D\u0430\u0447\u043D\u0438 \u0441 \u044D\u0442\u043E\u0433\u043E \u0442\u0435\u043A\u0441\u0442\u0430 \u0438 \u043F\u0440\u0430\u0432\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u0447\u0435\u043C\u0443 \u043A\u043E\u0434 \u043F\u0440\u044F\u043C\u043E \u041F\u0420\u041E\u0422\u0418\u0412\u041E\u0420\u0415\u0427\u0418\u0422: \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442, \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D, \u043F\u043E\u044F\u0432\u0438\u043B\u0441\u044F \u043F\u0430\u043A\u0435\u0442, \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0439 \u0437\u0435\u043B\u0435\u043D\u0438.",
11176
+ "\u041E\u0441\u0442\u0430\u043B\u044C\u043D\u043E\u0435 \u043E\u0441\u0442\u0430\u0432\u044C \u0414\u041E\u0421\u041B\u041E\u0412\u041D\u041E, \u0432\u043F\u043B\u043E\u0442\u044C \u0434\u043E \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043E\u043A. \u042D\u0442\u043E \u043D\u0435 \u043F\u0435\u0434\u0430\u043D\u0442\u0438\u0437\u043C: \u043F\u0440\u0430\u0432\u043A\u0443 \u0441\u043C\u043E\u0442\u0440\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0432 \u0434\u0438\u0444\u0444 PR, \u0438 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u0430\u043D\u043D\u0430\u044F \u0434\u0440\u0443\u0433\u0438\u043C\u0438 \u0441\u043B\u043E\u0432\u0430\u043C\u0438 \u0432\u0435\u0440\u043D\u0430\u044F \u0441\u0442\u0440\u043E\u043A\u0430 \u0432\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u0442\u0430\u043C \u0440\u043E\u0432\u043D\u043E \u043A\u0430\u043A \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0435 \u2014 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0430\u044F \u043F\u0440\u0430\u0432\u043A\u0430 \u0443\u0442\u043E\u043D\u0435\u0442 \u0441\u0440\u0435\u0434\u0438 \u043F\u0435\u0440\u0435\u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043E\u043A. \xAB\u0421\u0442\u0430\u043B\u043E \u043A\u0440\u0430\u0441\u0438\u0432\u0435\u0435\xBB \u2014 \u043D\u0435 \u043F\u043E\u0432\u043E\u0434 \u0434\u043B\u044F \u0441\u0442\u0440\u043E\u043A\u0438 \u0432 \u0434\u0438\u0444\u0444\u0435."
11177
+ ].join("\n");
10691
11178
  }
10692
- function parseScoutReport(text) {
10693
- const json = stripFence(text).trim();
10694
- if (!json) return { ok: false, error: "\u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 (\u0444\u0430\u0439\u043B \u043F\u0443\u0441\u0442)" };
10695
- let raw;
10696
- try {
10697
- raw = JSON.parse(json);
10698
- } catch (e) {
10699
- return { ok: false, error: `\u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F \u043A\u0430\u043A JSON: ${e.message}` };
10700
- }
10701
- if (raw == null || typeof raw !== "object" || Array.isArray(raw)) {
10702
- return { ok: false, error: "\u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C JSON-\u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \u0441 \u043F\u043E\u043B\u044F\u043C\u0438 \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0438" };
10703
- }
10704
- const src = raw;
10705
- const report = {};
10706
- const fields = [];
10707
- for (const f of SCOUT_FIELDS) {
10708
- const value = src[f];
10709
- if (value === void 0 || value === null) continue;
10710
- report[f] = value;
10711
- fields.push(f);
10712
- }
10713
- if (fields.length === 0) {
10714
- const foreign = Object.keys(src).filter((k) => !SCOUT_FIELDS.includes(k));
10715
- if (foreign.length) {
10716
- return { ok: false, error: `\u0432 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0438 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u043E\u043B\u044F \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0438 (\u0435\u0441\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E: ${foreign.slice(0, 5).join(", ")})` };
10717
- }
10718
- }
10719
- return { ok: true, report, fields };
11179
+ function parseBrief(text) {
11180
+ if (text == null) return { ok: false, error: "\u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A \u043D\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043B \u0444\u0430\u0439\u043B \u0431\u0440\u0438\u0444\u0430" };
11181
+ const body = stripFence(text).trim();
11182
+ if (!body) return { ok: false, error: "\u0444\u0430\u0439\u043B \u0431\u0440\u0438\u0444\u0430 \u043F\u0443\u0441\u0442" };
11183
+ if (body.startsWith("{")) return { ok: false, error: "\u0432 \u0444\u0430\u0439\u043B\u0435 \u0431\u0440\u0438\u0444\u0430 JSON, \u0430 \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F markdown" };
11184
+ return { ok: true, text: body };
10720
11185
  }
10721
11186
  function stripFence(text) {
10722
- const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(text);
11187
+ const t = text.trim();
11188
+ if (!t.startsWith("```")) return text;
11189
+ const fenced = /^```(?:markdown|md)?\s*([\s\S]*?)```\s*$/.exec(t);
10723
11190
  return fenced?.[1] ?? text;
10724
11191
  }
10725
- function renderScoutOutcome(repoFullName, diff) {
10726
- const label = { new: "\u043D\u043E\u0432\u043E\u0435", changed: "\u0438\u0437\u043C\u0435\u043D\u0438\u043B\u043E\u0441\u044C", same: "\u043A\u0430\u043A \u0431\u044B\u043B\u043E", gone: "\u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0435\u0442" };
10727
- const changed = diff.filter((d) => d.change === "new" || d.change === "changed");
10728
- if (!changed.length) {
10729
- return [`\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${repoFullName}: \u043D\u043E\u0432\u043E\u0433\u043E \u043D\u0438\u0447\u0435\u0433\u043E \u2014 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0441\u043E\u0432\u043F\u0430\u043B\u043E \u0441 \u0442\u0435\u043C, \u0447\u0442\u043E \u0443\u0436\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E.`];
10730
- }
10731
- const lines = [`\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${repoFullName}: \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u043E \u043A \u043F\u0440\u0438\u0451\u043C\u043A\u0435 ${changed.length} \u043F\u043E\u043B(\u044F/\u0435\u0439).`];
10732
- for (const d of changed) {
10733
- const human = d.overwritesHuman ? " \u26A0\uFE0F \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u0430\u043B\u043E \u0431\u044B \u043F\u0440\u0430\u0432\u043A\u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u2014 \u0440\u0435\u0448\u0438\u0442 \u043E\u043D \u0441\u0430\u043C" : "";
10734
- lines.push(` \u2022 ${d.field} \u2014 ${label[d.change] ?? d.change}${human}`);
10735
- }
10736
- const untouched = diff.filter((d) => d.change === "gone").map((d) => d.field);
10737
- if (untouched.length) lines.push(` \u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 \u043F\u0440\u043E\u043C\u043E\u043B\u0447\u0430\u043B\u0430 (\u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u043A\u0430\u043A \u0435\u0441\u0442\u044C): ${untouched.join(", ")}`);
10738
- lines.push("\u0416\u0438\u0432\u0430\u044F \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0430 \u041D\u0415 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0430: \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0435\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043D\u0430 \u044D\u043A\u0440\u0430\u043D\u0435 \u043F\u0440\u0438\u0451\u043C\u043A\u0438.");
10739
- return lines;
11192
+ function withBriefHeader(body) {
11193
+ const withoutHeader = body.replace(/^\s*<!--[\s\S]*?-->\s*/, (m) => m.includes("swarm scout") ? "" : m);
11194
+ return `${BRIEF_HEADER}
11195
+
11196
+ ${withoutHeader.trim()}
11197
+ `;
11198
+ }
11199
+ function ensureBriefImport(current) {
11200
+ if (current == null) {
11201
+ const text2 = [
11202
+ "# CLAUDE.md",
11203
+ "",
11204
+ "\u0417\u043D\u0430\u043D\u0438\u0435 \u043E\u0431 \u044D\u0442\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0434\u043B\u044F \u0430\u0433\u0435\u043D\u0442\u043E\u0432. \u0411\u0440\u0438\u0444 \u043D\u0438\u0436\u0435 \u0432\u0435\u0434\u0451\u0442 \u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A \u0440\u043E\u044F (`swarm scout`),",
11205
+ "\u043E\u0441\u0442\u0430\u043B\u044C\u043D\u043E\u0435 \u0432 \u044D\u0442\u043E\u043C \u0444\u0430\u0439\u043B\u0435 \u2014 \u0442\u0435\u0440\u0440\u0438\u0442\u043E\u0440\u0438\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430.",
11206
+ "",
11207
+ BRIEF_IMPORT,
11208
+ ""
11209
+ ].join("\n");
11210
+ return { text: text2, changed: true, created: true };
11211
+ }
11212
+ if (current.includes(BRIEF_IMPORT)) return { text: current, changed: false, created: false };
11213
+ const base3 = current.endsWith("\n") ? current : `${current}
11214
+ `;
11215
+ const text = `${base3}
11216
+ ${BRIEF_IMPORT}
11217
+ `;
11218
+ return { text, changed: true, created: false };
11219
+ }
11220
+ function renderScoutOutcome(repoFullName, opts) {
11221
+ return [
11222
+ `\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${repoFullName}: \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u043E ${opts.files.length} \u0444\u0430\u0439\u043B(\u043E\u0432) \u2014 ${opts.files.join(", ")}.`,
11223
+ `PR: ${opts.prUrl}`,
11224
+ "\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043B\u043E\u0441\u044C \u043C\u043E\u043B\u0447\u0430: \u043F\u043E\u043A\u0430 PR \u043D\u0435 \u0441\u043C\u0435\u0440\u0436\u0435\u043D, \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435 \u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435 \u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F."
11225
+ ];
10740
11226
  }
10741
11227
  async function runRepoScout(deps) {
10742
- const profile = await deps.readProfile();
11228
+ const briefPath = deps.briefPath ?? BRIEF_PATH;
11229
+ const before = deps.readFile(briefPath);
10743
11230
  deps.log(
10744
- profile ? `\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${deps.repoFullName}: \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0443\u0436\u0435 \u0440\u0430\u0437\u043C\u0435\u0447\u0435\u043D \u2014 \u0438\u0449\u0435\u043C \u0420\u0410\u0417\u041D\u0418\u0426\u0423 (\u0440\u0443\u0447\u043D\u044B\u0435 \u043F\u0440\u0430\u0432\u043A\u0438 \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0430 \u043D\u0435 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442 \u043C\u043E\u043B\u0447\u0430).` : `\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${deps.repoFullName}: \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0438 \u043D\u0435\u0442 \u2014 \u0440\u0430\u0437\u043C\u0435\u0447\u0430\u0435\u043C \u0432\u043F\u0435\u0440\u0432\u044B\u0435.`
11231
+ before ? `\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${deps.repoFullName}: \u0431\u0440\u0438\u0444 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u2014 \u0438\u0449\u0435\u043C, \u0447\u0442\u043E \u0432 \u043D\u0451\u043C \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u043E (${briefPath}).` : `\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${deps.repoFullName}: \u0431\u0440\u0438\u0444\u0430 \u043D\u0435\u0442 \u2014 \u043F\u0438\u0448\u0435\u043C \u0432\u043F\u0435\u0440\u0432\u044B\u0435 (${briefPath}).`
10745
11232
  );
10746
11233
  const prompt = buildScoutPrompt({
10747
11234
  repoFullName: deps.repoFullName,
10748
11235
  repoPath: deps.repoPath,
10749
- outFile: deps.outFile,
10750
- profile,
11236
+ briefPath,
11237
+ brief: before,
10751
11238
  ...deps.name ? { name: deps.name } : {}
10752
11239
  });
10753
- deps.clearOut();
10754
11240
  const code = await deps.runScout(prompt);
10755
- const raw = deps.readOut();
10756
- if (raw == null) {
10757
- deps.log(`\u0420\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A \u043D\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u043B \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F (${deps.outFile} \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u043D, \u043A\u043E\u0434 \u0432\u044B\u0445\u043E\u0434\u0430 ${code}). \u0420\u0430\u0437\u043C\u0435\u0442\u043A\u0430 \u043D\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0430.`);
10758
- return 1;
10759
- }
10760
- const parsed = parseScoutReport(raw);
11241
+ const parsed = parseBrief(deps.readFile(briefPath));
10761
11242
  if (!parsed.ok) {
10762
- deps.log(`\u041F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A\u0430 \u043D\u0435 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F: ${parsed.error}. \u0420\u0430\u0437\u043C\u0435\u0442\u043A\u0430 \u043D\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0430, \u0444\u0430\u0439\u043B \u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D: ${deps.outFile}`);
11243
+ deps.log(`\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${deps.repoFullName} \u043D\u0438 \u043A \u0447\u0435\u043C\u0443 \u043D\u0435 \u043F\u0440\u0438\u0432\u0435\u043B\u0430: ${parsed.error} (\u043A\u043E\u0434 \u0432\u044B\u0445\u043E\u0434\u0430 ${code}). \u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043C\u0435\u043D\u044F\u0435\u043C.`);
10763
11244
  return 1;
10764
11245
  }
10765
- if (parsed.fields.length === 0) {
11246
+ const files = [];
11247
+ const brief = withBriefHeader(parsed.text);
11248
+ if (brief !== before) {
11249
+ deps.writeFile(briefPath, brief);
11250
+ files.push(briefPath);
11251
+ }
11252
+ const claudeBefore = deps.readFile(CLAUDE_MD_PATH);
11253
+ const claude = ensureBriefImport(claudeBefore);
11254
+ if (claude.changed) {
11255
+ deps.writeFile(CLAUDE_MD_PATH, claude.text);
11256
+ files.push(CLAUDE_MD_PATH);
10766
11257
  deps.log(
10767
- profile ? `\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${deps.repoFullName}: \u0440\u0430\u0437\u043D\u0438\u0446\u044B \u043D\u0435\u0442 \u2014 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0441\u043E\u0432\u043F\u0430\u043B \u0441 \u0442\u0435\u043C, \u0447\u0442\u043E \u0443\u0436\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E. \u0420\u0430\u0437\u043C\u0435\u0442\u043A\u0430 \u043D\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0430.` : `\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${deps.repoFullName}: \u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A \u043D\u0435 \u043D\u0430\u0448\u0451\u043B, \u0447\u0442\u043E \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438\u0442\u044C, \u2014 \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0430 \u043E\u0441\u0442\u0430\u043B\u0430\u0441\u044C \u043F\u0443\u0441\u0442\u043E\u0439. \u042D\u0442\u043E \u043E\u0442\u0432\u0435\u0442, \u0430 \u043D\u0435 \u0441\u0431\u043E\u0439: \u043F\u0443\u0441\u0442\u043E\u0435 \u043F\u043E\u043B\u0435 \u0447\u0435\u0441\u0442\u043D\u0435\u0435 \u0432\u044B\u0434\u0443\u043C\u0430\u043D\u043D\u043E\u0433\u043E.`
11258
+ claude.created ? `${CLAUDE_MD_PATH} \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u0437\u0430\u0432\u0451\u043B \u043C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439, \u0441\u043E \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \u0438\u043C\u043F\u043E\u0440\u0442\u0430 \`${BRIEF_IMPORT}\`.` : `\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0432 ${CLAUDE_MD_PATH} \u0441\u0442\u0440\u043E\u043A\u0443 \u0438\u043C\u043F\u043E\u0440\u0442\u0430 \`${BRIEF_IMPORT}\` \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0430 \u043D\u0435 \u0442\u0440\u043E\u043D\u0435\u0442.`
10768
11259
  );
11260
+ }
11261
+ if (!files.length) {
11262
+ deps.log(`\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${deps.repoFullName}: \u0440\u0430\u0437\u043D\u0438\u0446\u044B \u043D\u0435\u0442 \u2014 \u0431\u0440\u0438\u0444 \u0441\u043E\u0432\u043F\u0430\u043B \u0441 \u0442\u0435\u043C, \u0447\u0442\u043E \u0443\u0436\u0435 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438. PR \u043D\u0435 \u043D\u0443\u0436\u0435\u043D.`);
10769
11263
  return 0;
10770
11264
  }
10771
- deps.log(`\u0420\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438\u043B \u043F\u043E\u043B\u044F: ${parsed.fields.join(", ")}. \u041E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u044E \u0432 Taskless \u2014 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0442\u044C \u0431\u0443\u0434\u0435\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A.`);
10772
- const sent = await deps.report(parsed.report);
11265
+ const sent = await deps.publish(files);
10773
11266
  if (!sent.ok) {
10774
- deps.log(`\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${sent.error}. \u041E\u043D\u043E \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C \u0432 ${deps.outFile} \u2014 \u043C\u043E\u0436\u043D\u043E \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0443.`);
11267
+ deps.log(`\u041E\u0442\u043A\u0440\u044B\u0442\u044C PR \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${sent.error}. \u041F\u0440\u0430\u0432\u043A\u0438 \u043E\u0441\u0442\u0430\u043B\u0438\u0441\u044C \u0432 \u0440\u0430\u0431\u043E\u0447\u0435\u0439 \u043A\u043E\u043F\u0438\u0438 ${deps.repoPath} \u2014 \u043C\u043E\u0436\u043D\u043E \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C.`);
10775
11268
  return 1;
10776
11269
  }
10777
- deps.clearOut();
10778
- for (const line of renderScoutOutcome(deps.repoFullName, sent.diff)) deps.log(line);
11270
+ if (!sent.prUrl) {
11271
+ deps.log(`\u0420\u0430\u0437\u0432\u0435\u0434\u043A\u0430 ${deps.repoFullName}: \u0440\u0430\u0437\u043D\u0438\u0446\u044B \u043D\u0435\u0442 \u2014 git \u043D\u0435 \u0443\u0432\u0438\u0434\u0435\u043B \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u0432 ${files.join(", ")}. PR \u043D\u0435 \u043D\u0443\u0436\u0435\u043D.`);
11272
+ return 0;
11273
+ }
11274
+ for (const line of renderScoutOutcome(deps.repoFullName, { files, prUrl: sent.prUrl })) deps.log(line);
10779
11275
  return 0;
10780
11276
  }
10781
11277
 
@@ -11288,7 +11784,7 @@ async function runDaemonLogs(argv) {
11288
11784
  console.error(`\u043B\u043E\u0433 \u041F\u0420\u041E\u0413\u041E\u041D\u0410 \u2014 \u044D\u0442\u043E \xABswarm log ${stray}\xBB (\u0431\u0435\u0437 s). \xABswarm logs\xBB \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430; \u0447\u0438\u0441\u043B\u043E \u0441\u0442\u0440\u043E\u043A \u2014 \xABswarm logs -n 200\xBB.`);
11289
11785
  return 1;
11290
11786
  }
11291
- if (!existsSync15(DAEMON_LOG_PATH)) {
11787
+ if (!existsSync16(DAEMON_LOG_PATH)) {
11292
11788
  console.error(`\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442: ${DAEMON_LOG_PATH}`);
11293
11789
  console.error("\u043E\u043D \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0444\u043E\u043D\u043E\u0432\u043E\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435 (swarm start). \u0414\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u043D\u044F\u0442 \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435 (--foreground) \u2014 \u0432\u044B\u0432\u043E\u0434 \u0438\u0434\u0451\u0442 \u0442\u0443\u0434\u0430.");
11294
11790
  return 1;
@@ -11296,12 +11792,12 @@ async function runDaemonLogs(argv) {
11296
11792
  const head = tailFile(DAEMON_LOG_PATH, lines);
11297
11793
  if (head) console.log(head);
11298
11794
  if (!follow) return 0;
11299
- let pos = statSync9(DAEMON_LOG_PATH).size;
11795
+ let pos = statSync10(DAEMON_LOG_PATH).size;
11300
11796
  for (; ; ) {
11301
11797
  await sleep2(500);
11302
11798
  let size;
11303
11799
  try {
11304
- size = statSync9(DAEMON_LOG_PATH).size;
11800
+ size = statSync10(DAEMON_LOG_PATH).size;
11305
11801
  } catch {
11306
11802
  continue;
11307
11803
  }
@@ -11494,11 +11990,12 @@ function usage() {
11494
11990
  \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u044C
11495
11991
  swarm ls [\u0441\u0442\u0430\u0442\u0443\u0441] \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u0436\u0438\u0432\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0440\u043E\u0432\u043D\u043E \u0442\u0435, \u0447\u0442\u043E \u0432 \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u0435
11496
11992
  swarm scout [--repo <path>]
11497
- \u0420\u0410\u0417\u0412\u0415\u0414\u041A\u0410 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F: \u0434\u0440\u043E\u043D \u0447\u0438\u0442\u0430\u0435\u0442 \u043A\u043E\u0434 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0438 \u043F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0435\u0442
11498
- \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u0443 \u2014 \u0437\u043E\u043D\u044B area:*, \u0447\u0435\u043C \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435, \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0438 \u0447\u0442\u043E
11499
- \u0437\u0434\u0435\u0441\u044C \u0437\u0435\u043B\u0451\u043D\u043E\u0435, \u043A\u043E\u043D\u0432\u0435\u043D\u0446\u0438\u0438, \u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442, \u0431\u0440\u0438\u0444. \u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0435\u0442:
11500
- \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0432 Taskless (\u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430).
11501
- \u041F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u0437\u0430\u043F\u0443\u0441\u043A \u043D\u0430 \u0440\u0430\u0437\u043C\u0435\u0447\u0435\u043D\u043D\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0440\u0430\u0437\u043D\u0438\u0446\u0443
11993
+ \u0420\u0410\u0417\u0412\u0415\u0414\u041A\u0410 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F: \u0434\u0440\u043E\u043D \u0447\u0438\u0442\u0430\u0435\u0442 \u043A\u043E\u0434 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0438 \u043F\u0438\u0448\u0435\u0442
11994
+ docs/AGENT_BRIEF.md \u2014 \u0447\u0435\u043C \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435, \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0438 \u0447\u0442\u043E
11995
+ \u0437\u0434\u0435\u0441\u044C \u0437\u0435\u043B\u0451\u043D\u043E\u0435, \u0441\u043E\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u044F, \u0437\u043E\u043D\u044B \u043A\u043E\u0434\u0430, \u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442. CLAUDE.md
11996
+ \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0444\u0430\u0439\u043B \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \u0438\u043C\u043F\u043E\u0440\u0442\u0430 (\u043E\u0434\u0438\u043D \u0440\u0430\u0437, \u0434\u0430\u043B\u044C\u0448\u0435 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0435\u0442\u0441\u044F).
11997
+ \u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0435\u0442 \u043C\u043E\u043B\u0447\u0430: \u043E\u0442\u0434\u0430\u0451\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C PR, \u043C\u0435\u0440\u0436\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A.
11998
+ \u041F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0444\u0430\u0439\u043B \u0431\u0440\u0438\u0444\u0430
11502
11999
  swarm zones [check|suggest <\u0442\u0435\u043A\u0441\u0442|slug>]
11503
12000
  \u0437\u043E\u043D\u044B \u043A\u043E\u0434\u0430, \u0432\u044B\u0432\u0435\u0434\u0435\u043D\u043D\u044B\u0435 \u0438\u0437 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0440\u0435\u043F\u043E (\u043A\u0430\u043A\u0438\u0435 \u0444\u0430\u0439\u043B\u044B \u043C\u0435\u043D\u044F\u044E\u0442\u0441\u044F
11504
12001
  \u0432\u043C\u0435\u0441\u0442\u0435, \u0447\u0442\u043E \u0442\u0443\u0442 \u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442). check \u2014 \u0441\u0432\u0435\u0440\u043A\u0430 \u0441 \u0440\u0443\u0447\u043D\u043E\u0439
@@ -11560,10 +12057,10 @@ function effectiveWorktreesDir() {
11560
12057
  legacyDir: WORKTREES_LEGACY_DIR,
11561
12058
  targetDir: WORKTREES_DIR,
11562
12059
  envOverride: process.env.SWARM_WORKTREES || "",
11563
- exists: (d) => existsSync15(d),
12060
+ exists: (d) => existsSync16(d),
11564
12061
  isNonEmptyDir: (d) => {
11565
12062
  try {
11566
- return readdirSync9(d).length > 0;
12063
+ return readdirSync10(d).length > 0;
11567
12064
  } catch {
11568
12065
  return false;
11569
12066
  }
@@ -11588,29 +12085,50 @@ async function makeCliStore(repo) {
11588
12085
  client.reconcileRuntime({ worktreesDir: effectiveWorktreesDir(), listProcesses: listDroneProcesses });
11589
12086
  return client;
11590
12087
  }
12088
+ var SCOUT_BRANCH = "swarm-scout/agent-brief";
11591
12089
  async function runScoutCommand(repo) {
12090
+ try {
12091
+ return await scoutRepo(repo);
12092
+ } catch (e) {
12093
+ const msg = e.message;
12094
+ console.error(`\u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0430 \u043D\u0435 \u043F\u043E\u0448\u043B\u0430: ${msg}`);
12095
+ if (e instanceof WorktreeBusyError) {
12096
+ console.error(`\u0440\u0430\u0431\u043E\u0447\u0430\u044F \u043A\u043E\u043F\u0438\u044F \u043F\u0440\u043E\u0448\u043B\u043E\u0439 \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0438 \u0437\u0430\u043D\u044F\u0442\u0430 \u0438\u043B\u0438 \u0432 \u043D\u0435\u0439 \u043E\u0441\u0442\u0430\u043B\u0438\u0441\u044C \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 \u0435\u0451 \u0438 \u0441\u043D\u0435\u0441\u0438: git worktree remove --force <\u043F\u0443\u0442\u044C \u0432\u044B\u0448\u0435>`);
12097
+ }
12098
+ return 1;
12099
+ }
12100
+ }
12101
+ async function scoutRepo(repo) {
11592
12102
  const repoFullName = await remoteRepoFullName(repo);
11593
12103
  if (!repoFullName) {
11594
- console.error(`\u043D\u0435 \u043F\u043E\u043D\u044F\u043B, \u0447\u0442\u043E \u0437\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0432 ${repo}: \u0443 origin \u043D\u0435\u0442 GitHub-\u0430\u0434\u0440\u0435\u0441\u0430. \u0420\u0430\u0437\u043C\u0435\u0442\u043A\u0430 \u0430\u0434\u0440\u0435\u0441\u0443\u0435\u0442\u0441\u044F \u043F\u043E owner/repo.`);
12104
+ console.error(`\u043D\u0435 \u043F\u043E\u043D\u044F\u043B, \u0447\u0442\u043E \u0437\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0432 ${repo}: \u0443 origin \u043D\u0435\u0442 GitHub-\u0430\u0434\u0440\u0435\u0441\u0430. PR \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0438 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u043A\u0443\u0434\u0430.`);
11595
12105
  return 1;
11596
12106
  }
11597
- const call = resilientSwarmMcp({
11598
- onDown: (ms) => console.warn(`Swarm MCP \u043C\u043E\u043B\u0447\u0438\u0442 ${Math.round(ms / 1e3)}\u0441 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0434\u043E\u043C.`),
11599
- onUp: () => console.warn("Swarm MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
11600
- });
11601
- const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, autoFlush: false });
11602
12107
  const name = pickName([]);
11603
- const outFile = join23(SWARM_HOME, `scout-${repoFullName.replace(/[^\w.-]+/g, "-")}.json`);
11604
12108
  const bin = resolveExecutor().resolveBin();
12109
+ const worktree = droneWorktreePath(effectiveWorktreesDir(), repo, "swarm-scout");
12110
+ await $6({ reject: false })`git -C ${repo} fetch --quiet origin`;
12111
+ const baseRef = await remoteMainRef(repo);
12112
+ const baseBranch = baseRef.replace(/^origin\//, "");
12113
+ const openPr = await scoutPrState(repo);
12114
+ await addWorktree(repo, worktree, SCOUT_BRANCH, { base: baseRef, isWorktreeHeldLive: async () => false });
12115
+ if (openPr.known && !openPr.url) {
12116
+ await $6({ reject: false })`git -C ${worktree} reset --hard ${baseRef}`;
12117
+ }
12118
+ await setWorktreeIdentity(worktree, name);
12119
+ const abs = (rel) => join24(worktree, rel);
11605
12120
  const code = await runRepoScout({
11606
12121
  repoFullName,
11607
- repoPath: repo,
12122
+ repoPath: worktree,
11608
12123
  name,
11609
- outFile,
11610
- readProfile: () => client.repoProfile(repoFullName),
12124
+ readFile: (rel) => existsSync16(abs(rel)) ? readFileSync17(abs(rel), "utf8") : null,
12125
+ writeFile: (rel, text) => {
12126
+ mkdirSync9(dirname4(abs(rel)), { recursive: true });
12127
+ writeFileSync13(abs(rel), text);
12128
+ },
11611
12129
  runScout: (prompt) => new Promise((resolve4) => {
11612
12130
  const child = spawn3(bin, [...ISOLATION_FLAGS, "--dangerously-skip-permissions", "-p", prompt], {
11613
- cwd: repo,
12131
+ cwd: worktree,
11614
12132
  stdio: ["ignore", "inherit", "inherit"]
11615
12133
  // человек смотрит на разведку живьём
11616
12134
  });
@@ -11620,13 +12138,65 @@ async function runScoutCommand(repo) {
11620
12138
  });
11621
12139
  child.on("exit", (c) => resolve4(c ?? 1));
11622
12140
  }),
11623
- readOut: () => existsSync15(outFile) ? readFileSync17(outFile, "utf8") : null,
11624
- clearOut: () => rmSync2(outFile, { force: true }),
11625
- report: (report) => client.reportRepoScout(repoFullName, report, name),
12141
+ publish: (files) => publishScoutPr({ worktree, repo, files, name, baseBranch, openPr: openPr.url, fresh: openPr.known && !openPr.url }),
11626
12142
  log: (line) => console.log(line)
11627
12143
  });
12144
+ if (code === 0) await removeWorktree(repo, worktree);
11628
12145
  return code;
11629
12146
  }
12147
+ async function scoutPrState(repo) {
12148
+ const res = await $6({
12149
+ cwd: repo,
12150
+ reject: false
12151
+ })`gh pr list --head ${SCOUT_BRANCH} --state open --json url`;
12152
+ if (res.exitCode !== 0) return { known: false, url: null };
12153
+ try {
12154
+ const arr = JSON.parse(res.stdout);
12155
+ const url = Array.isArray(arr) ? arr.find((p) => typeof p.url === "string")?.url : void 0;
12156
+ return { known: true, url: url ?? null };
12157
+ } catch {
12158
+ return { known: false, url: null };
12159
+ }
12160
+ }
12161
+ async function publishScoutPr(opts) {
12162
+ const { worktree, files, name, baseBranch } = opts;
12163
+ const message = [
12164
+ `docs(agents): \u0431\u0440\u0438\u0444 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u0434\u043B\u044F \u0430\u0433\u0435\u043D\u0442\u043E\u0432 (${BRIEF_PATH})`,
12165
+ "",
12166
+ "\u0427\u0442\u043E \u0434\u0430\u043B\u044C\u0448\u0435 \u0434\u0435\u043B\u0430\u0435\u0442 \u0440\u043E\u0439 \u0441 \u044D\u0442\u0438\u043C \u0444\u0430\u0439\u043B\u043E\u043C: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0435\u0433\u043E \u0438\u043C\u043F\u043E\u0440\u0442\u043E\u043C \u0438\u0437 CLAUDE.md, \u043F\u043E\u044D\u0442\u043E\u043C\u0443",
12167
+ "\u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u0435 \u043F\u043E\u043F\u0430\u0434\u0430\u0435\u0442 \u0432 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043A\u0430\u0436\u0434\u043E\u0439 \u0430\u0433\u0435\u043D\u0442\u0441\u043A\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0441\u0430\u043C\u043E, \u0431\u0435\u0437 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u044F \u043A \u0442\u0443\u043B\u0430\u043C.",
12168
+ "",
12169
+ `Scout: ${name}`
12170
+ ].join("\n");
12171
+ const add = await $6({ cwd: worktree, reject: false })`git add -- ${files}`;
12172
+ if (add.exitCode !== 0) return { ok: false, error: `git add: ${add.stderr || add.stdout}` };
12173
+ const staged = await $6({ cwd: worktree, reject: false })`git diff --cached --quiet`;
12174
+ if (staged.exitCode === 0) return { ok: true, prUrl: null };
12175
+ const commit = await $6({ cwd: worktree, reject: false })`git commit --no-verify -m ${message}`;
12176
+ if (commit.exitCode !== 0) return { ok: false, error: `git commit: ${commit.stderr || commit.stdout}` };
12177
+ const push = opts.fresh ? await $6({ cwd: worktree, reject: false })`git push --force-with-lease origin ${`HEAD:${SCOUT_BRANCH}`}` : await $6({ cwd: worktree, reject: false })`git push origin ${`HEAD:${SCOUT_BRANCH}`}`;
12178
+ if (push.exitCode !== 0) return { ok: false, error: `git push: ${push.stderr || push.stdout}` };
12179
+ if (opts.openPr) return { ok: true, prUrl: opts.openPr };
12180
+ const body = [
12181
+ `\u0411\u0440\u0438\u0444 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u0434\u043B\u044F \u0430\u0433\u0435\u043D\u0442\u043E\u0432: \u0447\u0435\u043C \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435, \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0438 \u0447\u0442\u043E \u0437\u0434\u0435\u0441\u044C \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C, \u0441\u043E\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u044F, \u0437\u043E\u043D\u044B \u043A\u043E\u0434\u0430 \u0438 \u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442. \u041D\u0430\u043F\u0438\u0441\u0430\u043D \u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A\u043E\u043C \u0440\u043E\u044F (\`swarm scout\`) \u043F\u043E \u043A\u043E\u0434\u0443 \u044D\u0442\u043E\u0433\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F.`,
12182
+ "",
12183
+ `\`${BRIEF_PATH}\` \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\u0438\u0442 \u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A\u0443 \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u043F\u0440\u043E\u0445\u043E\u0434 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0438\u0448\u0435\u0442 \u0435\u0433\u043E \u0446\u0435\u043B\u0438\u043A\u043E\u043C, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043F\u0440\u0430\u0432\u043A\u0438 \u0440\u0443\u043A\u0430\u043C\u0438 \u0432 \u043D\u0435\u0433\u043E \u043D\u0435 \u0432\u043D\u043E\u0441\u044F\u0442. \`CLAUDE.md\` \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0444\u0430\u0439\u043B \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \`${BRIEF_IMPORT}\`: \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u0435 \u043F\u043E\u043F\u0430\u0434\u0430\u0435\u0442 \u0432 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0430\u0433\u0435\u043D\u0442\u0441\u043A\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0441\u0430\u043C\u043E, \u0430 \u043D\u0435 \xAB\u0435\u0441\u043B\u0438 \u0430\u0433\u0435\u043D\u0442 \u0432\u0441\u043F\u043E\u043C\u043D\u0438\u0442 \u0437\u0430\u0433\u043B\u044F\u043D\u0443\u0442\u044C\xBB. \u042D\u0442\u0443 \u0441\u0442\u0440\u043E\u043A\u0443 \u0440\u043E\u0439 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0435\u0442 \u043E\u0434\u0438\u043D \u0440\u0430\u0437; \u0434\u0430\u043B\u044C\u0448\u0435 \`CLAUDE.md\` \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u0442\u0435\u0440\u0440\u0438\u0442\u043E\u0440\u0438\u0435\u0439 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u0438 \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u043E\u0439 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0435\u0442\u0441\u044F.`,
12184
+ "",
12185
+ `\u{1F41D} \u0420\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A ${name}`
12186
+ ].join("\n");
12187
+ const create = await $6({
12188
+ cwd: worktree,
12189
+ reject: false
12190
+ })`gh pr create --base ${baseBranch} --head ${SCOUT_BRANCH} --title ${`docs(agents): \u0431\u0440\u0438\u0444 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u0434\u043B\u044F \u0430\u0433\u0435\u043D\u0442\u043E\u0432 [${name}]`} --body ${body}`;
12191
+ if (create.exitCode !== 0) return { ok: false, error: `gh pr create: ${create.stderr || create.stdout}` };
12192
+ const url = /https:\/\/github\.com\/\S+\/pull\/\d+/.exec(create.stdout)?.[0];
12193
+ if (!url) {
12194
+ const found = (await openPrsForBranch(SCOUT_BRANCH, opts.repo))[0];
12195
+ if (found) return { ok: true, prUrl: found };
12196
+ return { ok: false, error: `gh pr create \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 PR: ${create.stdout}` };
12197
+ }
12198
+ return { ok: true, prUrl: url };
12199
+ }
11630
12200
  async function runZones(db2, repo, args) {
11631
12201
  const manual = new Map(db2.listRuns().map((r) => [r.task_slug, r.area]));
11632
12202
  const raw = await readTaskHistory(repo);
@@ -11696,7 +12266,7 @@ async function runZones(db2, repo, args) {
11696
12266
  }
11697
12267
  if (model.spine.length) {
11698
12268
  console.log(`
11699
- \u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442 (${model.spine.length} \u0444\u0430\u0439\u043B\u043E\u0432) \u2014 \u0437\u0430\u0434\u0430\u0447\u0438 \u043F\u0440\u043E \u043D\u0435\u0433\u043E \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:`);
12269
+ \u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442 (${model.spine.length} \u0444\u0430\u0439\u043B\u043E\u0432) \u2014 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u041E\u0414\u041D\u041E\u041C \u0442\u0430\u043A\u043E\u043C \u0444\u0430\u0439\u043B\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:`);
11700
12270
  for (const f of model.spine.slice(0, 15)) console.log(` ${f}`);
11701
12271
  }
11702
12272
  console.log("\n\u0441\u0432\u0435\u0440\u0438\u0442\u044C \u0441 \u0440\u0443\u0447\u043D\u043E\u0439 \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u043E\u0439: swarm zones check");
@@ -12017,6 +12587,8 @@ switch (cmd) {
12017
12587
  overmindEntry: OVERMIND_ENTRY,
12018
12588
  dbPath: DB_PATH,
12019
12589
  runLogsDir: RUN_LOGS_DIR,
12590
+ // TASWM-419: каталог лога — по стабильному ключу прогона (тот же адрес, что у демона).
12591
+ runLogKeys: (r) => db.runLogKeys(r.id),
12020
12592
  // Тот же исполнитель, что у демона (TASWM-349): `swarm wake` при мёртвом демоне не
12021
12593
  // должен внезапно поднимать дрона другим харнессом.
12022
12594
  executor: EXECUTOR,
@@ -12127,9 +12699,9 @@ switch (cmd) {
12127
12699
  // swarm zones — какие зоны видит рой и что считает общим хребтом
12128
12700
  // swarm zones check — сверка с ручной разметкой area:* на прошлых задачах
12129
12701
  // swarm zones suggest <текст|slug> — какую зону предложить задаче ПРИ ПОСТАНОВКЕ
12130
- // TASWM-379/382: РАЗВЕДКА репозитория — одноразовый прогон, после которого рой знает
12131
- // про чужой репозиторий то, что на taskless записано в CLAUDE.md и скиллах. Предложение
12132
- // разведчика НЕ применяется: его принимает человек (см. repoScout.ts).
12702
+ // TASWM-379/382/441: РАЗВЕДКА репозитория — прогон, после которого рой знает про чужой
12703
+ // репозиторий то, что на taskless записано в CLAUDE.md и скиллах. Знание ложится В САМ
12704
+ // репозиторий (docs/AGENT_BRIEF.md + импорт из CLAUDE.md) и отдаётся PR (см. repoScout.ts).
12133
12705
  case "scout": {
12134
12706
  const { repo: repoFlag, rest } = takeRepo(rawArgs);
12135
12707
  const repo = repoFlag ?? rest[0] ?? SWARM_REPO;