// Fixture data for WorkflowTools-Watchdog dashboard. // All times are anchored to a synthetic "now" so the UI feels alive. const NOW = new Date("2026-05-17T14:35:00Z"); // --------------------------------------------------------------------------- // Team — 6 agents // --------------------------------------------------------------------------- const TEAM = [ { id: "lev", name: "Лев", role: "architect", roleLabel: "Architect Lead", avatar: "avatars/lev.jpg", isLead: true, email: "lev@multi-print.local", planeUserId: "plane_3f1a", envKey: "PLANE_API_KEY_LEV", watcherOnline: true, watcherPid: 19847, watcherUptimeMin: 412, lastHeartbeatMin: 0.3, currentTask: { seq: 51, title: "Описать модуль уведомлений в KALK" }, contextPct: 78, avgToolCalls: 14, health: "ok", modulesCurrent: ["kalk", "notifications", "spec"], modulesHistorical: [ { module: "orders", lastSeen: "2026-05-16" }, { module: "seo", lastSeen: "2026-05-14" }, { module: "auth", lastSeen: "2026-05-12" }, { module: "tg-bot", lastSeen: "2026-05-10" }, ], enabled: true, contextHistory: [82, 80, 78, 90, 88, 85, 82, 79, 76, 74, 80, 78], todayLoad: [0.2, 0.3, 0.4, 0.6, 0.8, 0.9, 0.7, 0.5, 0.4, 0.3, 0.2, 0.1], }, { id: "kostya", name: "Костя", role: "coder", roleLabel: "Coder", avatar: "avatars/kostya.jpg", isLead: false, email: "kostya@multi-print.local", planeUserId: "plane_4b22", envKey: "PLANE_API_KEY_KOSTYA", watcherOnline: true, watcherPid: 19850, watcherUptimeMin: 412, lastHeartbeatMin: 0.8, currentTask: { seq: 49, title: "Ребрендинг Print-X в коде сайта" }, contextPct: 42, avgToolCalls: 28, health: "warn", modulesCurrent: ["next.js", "fastapi", "kalk"], modulesHistorical: [ { module: "seo", lastSeen: "2026-05-17" }, { module: "orders", lastSeen: "2026-05-16" }, { module: "kalk", lastSeen: "2026-05-15" }, { module: "tg-bot", lastSeen: "2026-05-14" }, ], enabled: true, contextHistory: [95, 90, 85, 78, 72, 68, 64, 60, 55, 50, 46, 42], todayLoad: [0.4, 0.5, 0.6, 0.8, 0.9, 1.0, 0.95, 0.9, 0.85, 0.8, 0.75, 0.7], }, { id: "roma", name: "Рома", role: "reviewer", roleLabel: "Reviewer", avatar: "avatars/roma.jpg", isLead: false, email: "roma@multi-print.local", planeUserId: "plane_5c83", envKey: "PLANE_API_KEY_ROMA", watcherOnline: true, watcherPid: 19853, watcherUptimeMin: 412, lastHeartbeatMin: 0.4, currentTask: { seq: 46, title: "Code review: миграция БД на v3" }, contextPct: 65, avgToolCalls: 18, health: "ok", modulesCurrent: ["db", "migrations", "fastapi"], modulesHistorical: [ { module: "next.js", lastSeen: "2026-05-17" }, { module: "kalk", lastSeen: "2026-05-16" }, { module: "tg-bot", lastSeen: "2026-05-15" }, ], enabled: true, contextHistory: [80, 78, 74, 72, 70, 68, 66, 65, 64, 63, 64, 65], todayLoad: [0.1, 0.2, 0.3, 0.5, 0.7, 0.6, 0.5, 0.4, 0.5, 0.6, 0.5, 0.4], }, { id: "timur", name: "Тимур", role: "tester", roleLabel: "Tester", avatar: "avatars/timur.jpg", isLead: false, email: "timur@multi-print.local", planeUserId: "plane_6d14", envKey: "PLANE_API_KEY_TIMUR", watcherOnline: true, watcherPid: 19856, watcherUptimeMin: 412, lastHeartbeatMin: 0.5, currentTask: null, contextPct: 88, avgToolCalls: 12, health: "ok", modulesCurrent: ["e2e", "playwright"], modulesHistorical: [ { module: "orders", lastSeen: "2026-05-17" }, { module: "kalk", lastSeen: "2026-05-15" }, { module: "auth", lastSeen: "2026-05-13" }, ], enabled: true, contextHistory: [90, 90, 88, 88, 90, 92, 90, 88, 88, 86, 87, 88], todayLoad: [0.0, 0.1, 0.2, 0.4, 0.5, 0.3, 0.2, 0.1, 0.2, 0.2, 0.1, 0.0], }, { id: "semyon", name: "Семён", role: "scout", roleLabel: "Scout", avatar: "avatars/semyon.jpg", isLead: false, email: "semyon@multi-print.local", planeUserId: "plane_7e95", envKey: "PLANE_API_KEY_SEMYON", watcherOnline: false, watcherPid: null, watcherUptimeMin: 0, lastHeartbeatMin: 47, currentTask: { seq: 50, title: "Подобрать референсы для каталога A4" }, contextPct: 18, avgToolCalls: 22, health: "crit", modulesCurrent: ["research", "seo"], modulesHistorical: [ { module: "tg-bot", lastSeen: "2026-05-16" }, { module: "seo", lastSeen: "2026-05-15" }, { module: "content", lastSeen: "2026-05-13" }, ], enabled: true, contextHistory: [70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 22, 18], todayLoad: [0.5, 0.6, 0.7, 0.8, 0.6, 0.4, 0.2, 0.1, 0.0, 0.0, 0.0, 0.0], }, { id: "viktor", name: "Виктор", role: "workflow-lead", roleLabel: "Workflow Lead", avatar: "avatars/viktor.jpg", isLead: true, email: "viktor@multi-print.local", planeUserId: "plane_8f06", envKey: "PLANE_API_KEY_VIKTOR", watcherOnline: true, watcherPid: 19862, watcherUptimeMin: 412, lastHeartbeatMin: 0.2, currentTask: { seq: 52, title: "Разбор алертов за смену" }, contextPct: 72, avgToolCalls: 16, health: "ok", modulesCurrent: ["watchdog", "rules", "spec"], modulesHistorical: [ { module: "kalk", lastSeen: "2026-05-16" }, { module: "orders", lastSeen: "2026-05-15" }, { module: "auth", lastSeen: "2026-05-12" }, ], enabled: true, contextHistory: [85, 82, 80, 78, 76, 74, 73, 72, 71, 71, 72, 72], todayLoad: [0.3, 0.4, 0.5, 0.6, 0.7, 0.5, 0.6, 0.7, 0.6, 0.5, 0.4, 0.5], }, ]; function teamById(id) { return TEAM.find(t => t.id === id); } // --------------------------------------------------------------------------- // Alerts // --------------------------------------------------------------------------- const ALERTS = [ { id: 17, ts: "2026-05-17T14:23:01Z", seq: 49, title: "Ребрендинг Print-X в коде сайта", rule: "coder_claim_within_5min", ruleTitle: "Coder не подобрал задачу", severity: "warning", agent: "kostya", text: "Костя не подобрал PB-49 за 12 мин после установки лейбла for-coder.", status: "active", durationMin: 12, claudeMdRef: "agents/coder/CLAUDE.md § 3.1 — Подбор задач", claudeMdQuote: "Если задача помечена `for-coder` и её state = Todo, ты обязан забрать её (assign на себя + перевести в In Progress) в течение 5 минут. Если занят — оставь комментарий 'busy, eta NN min'.", promptDraft: "Привет. Костя не подобрал PB-49 «Ребрендинг Print-X» за 12 мин после установки лейбла for-coder. Watcher Кости онлайн, heartbeat 0.8s назад, контекст 42%. Проверь, не висит ли он в текущей задаче, и реши: пинговать или переназначить.", }, { id: 16, ts: "2026-05-17T14:08:11Z", seq: 50, title: "Подобрать референсы для каталога A4", rule: "watcher_heartbeat_missing", ruleTitle: "Watcher не отвечает", severity: "critical", agent: "semyon", text: "Watcher Семёна не отвечает 47 минут. Последний heartbeat: 13:48 UTC.", status: "active", durationMin: 47, claudeMdRef: "agents/scout/CLAUDE.md § 1.2 — Жизненный цикл сессии", claudeMdQuote: "Сессия scout-агента должна работать непрерывно, отправляя heartbeat в watcher раз в 30 секунд. Если сессия завершилась — перезапусти её через `wt agent restart scout`.", promptDraft: "Семён — watcher offline 47 мин (последний heartbeat 13:48). Контекст был 18%, держал PB-50. Похоже на context-exhaustion. Перезапусти сессию и подними задачу с того же места.", }, { id: 15, ts: "2026-05-17T13:51:44Z", seq: 47, title: "Аналитика заказов: фильтр по статусу", rule: "stuck_in_progress_3h", ruleTitle: "Задача висит в In Progress", severity: "warning", agent: "kostya", text: "PB-47 в статусе In Progress 3ч 12мин без новых коммитов.", status: "active", durationMin: 192, claudeMdRef: "agents/coder/CLAUDE.md § 4.3 — Прогресс по задаче", claudeMdQuote: "Если задача в In Progress дольше 2 часов без коммита — оставь промежуточный коммент `progress: <что сделано>, <что осталось>`. Если застрял — переведи в Blocked.", promptDraft: "PB-47 у Кости висит 3ч без коммитов. Узнай, не застрял ли он на интеграции с FastAPI. Если да — попроси перевести в Blocked и описать что нужно от Лёва.", }, { id: 14, ts: "2026-05-17T12:14:02Z", seq: 46, title: "Code review: миграция БД на v3", rule: "reviewer_pickup_within_1h", ruleTitle: "Reviewer не дошёл до задачи", severity: "info", agent: "roma", text: "Лейбл for-reviewer установлен в 11:14, Рома подобрал в 12:14 (на грани).", status: "resolved", durationMin: 60, claudeMdRef: "agents/reviewer/CLAUDE.md § 2.1 — SLA на ревью", claudeMdQuote: "Задачу с лейблом for-reviewer необходимо взять в ревью в течение 1 часа от установки лейбла.", promptDraft: "Информационно: Рома взял PB-46 на ревью точно через 60 мин — на грани SLA. Если повторится, надо обсудить нагрузку.", }, { id: 13, ts: "2026-05-17T10:42:18Z", seq: 44, title: "Telegram-бот: подсчёт стоимости", rule: "labels_conflict", ruleTitle: "Конфликт лейблов", severity: "warning", agent: null, text: "На PB-44 одновременно for-coder и for-reviewer. Архитектурная ошибка.", status: "dismissed", durationMin: 7, claudeMdRef: "agents/architect/CLAUDE.md § 5 — Лейблы", claudeMdQuote: "В любой момент на задаче должен быть ровно один лейбл `for-`. Перед сменой роли убери предыдущий лейбл.", promptDraft: "PB-44 имела конфликт лейблов 7 мин (for-coder + for-reviewer). Кто-то проставил оба разом. Проверь логи.", }, ]; // --------------------------------------------------------------------------- // Events stream // --------------------------------------------------------------------------- const EVENTS = [ { ts: "2026-05-17T14:31:22Z", seq: 51, title: "Описать модуль уведомлений в KALK", kind: "new_task", agent: "lev", text: "Лев создал PB-51 «Описать модуль уведомлений в KALK»" }, { ts: "2026-05-17T14:28:04Z", seq: 49, title: "Ребрендинг Print-X в коде сайта", kind: "comment", agent: "kostya", text: "Костя оставил коммент: progress: правлю Footer.tsx, осталось header" }, { ts: "2026-05-17T14:23:01Z", seq: 49, title: "Ребрендинг Print-X в коде сайта", kind: "alert", agent: "kostya", text: "Сработало правило coder_claim_within_5min" }, { ts: "2026-05-17T14:20:33Z", seq: 49, title: "Ребрендинг Print-X в коде сайта", kind: "state_changed", agent: "kostya", text: "Костя перевёл PB-49: Todo → In Progress" }, { ts: "2026-05-17T14:18:11Z", seq: 49, title: "Ребрендинг Print-X в коде сайта", kind: "assignees_changed", agent: "kostya", text: "Костя взял PB-49 в работу" }, { ts: "2026-05-17T14:14:50Z", seq: 49, title: "Ребрендинг Print-X в коде сайта", kind: "labels_changed", agent: "lev", text: "Лев поставил лейбл for-coder на PB-49" }, { ts: "2026-05-17T14:08:11Z", seq: 50, title: "Подобрать референсы для каталога A4", kind: "alert", agent: "semyon", text: "Critical: watcher Семёна не отвечает 47 мин" }, { ts: "2026-05-17T13:58:02Z", seq: 46, title: "Code review: миграция БД на v3", kind: "state_changed", agent: "roma", text: "Рома approved PB-46: In Review → Done" }, { ts: "2026-05-17T13:51:44Z", seq: 47, title: "Аналитика заказов: фильтр по статусу", kind: "alert", agent: "kostya", text: "Сработало правило stuck_in_progress_3h" }, { ts: "2026-05-17T13:45:12Z", seq: 52, title: "Разбор алертов за смену", kind: "state_changed", agent: "viktor", text: "Виктор перевёл PB-52: Todo → In Progress" }, { ts: "2026-05-17T13:31:08Z", seq: 48, title: "SEO: meta-теги для каталога", kind: "state_changed", agent: "roma", text: "Рома approved PB-48: In Review → Done" }, { ts: "2026-05-17T13:14:33Z", seq: 46, title: "Code review: миграция БД на v3", kind: "assignees_changed", agent: "roma", text: "Рома взял PB-46 на ревью" }, { ts: "2026-05-17T13:08:51Z", seq: 46, title: "Code review: миграция БД на v3", kind: "labels_changed", agent: "kostya", text: "Костя поставил лейбл for-reviewer на PB-46" }, { ts: "2026-05-17T12:54:14Z", seq: 52, title: "Разбор алертов за смену", kind: "new_task", agent: "viktor", text: "Виктор создал PB-52" }, { ts: "2026-05-17T12:42:08Z", seq: 48, title: "SEO: meta-теги для каталога", kind: "assignees_changed", agent: "roma", text: "Рома взял PB-48 на ревью" }, { ts: "2026-05-17T12:14:02Z", seq: 46, title: "Code review: миграция БД на v3", kind: "alert", agent: "roma", text: "Сработало правило reviewer_pickup_within_1h (info)" }, { ts: "2026-05-17T11:31:01Z", seq: 45, title: "Onboarding wizard для KALK", kind: "state_changed", agent: "roma", text: "Рома approved PB-45: In Review → Done" }, { ts: "2026-05-17T11:08:44Z", seq: 45, title: "Onboarding wizard для KALK", kind: "comment", agent: "roma", text: "Рома: «LGTM, только переименуй handleSubmit2»" }, { ts: "2026-05-17T10:42:18Z", seq: 44, title: "Telegram-бот: подсчёт стоимости", kind: "alert", agent: null, text: "Сработало правило labels_conflict (dismissed)" }, { ts: "2026-05-17T10:14:00Z", seq: 51, title: "Описать модуль уведомлений в KALK", kind: "labels_changed", agent: "viktor", text: "Виктор поставил for-architect на PB-51" }, ]; // --------------------------------------------------------------------------- // Rules // --------------------------------------------------------------------------- const RULES = [ { name: "coder_claim_within_5min", description: "Если задача с лейблом for-coder висит в Todo > 5 минут без claim — алерт.", severity: "warning", active: true, triggers24h: 3, lastTriggered: "2026-05-17T14:23:01Z", role: "coder", claudeMdRef: "agents/coder/CLAUDE.md § 3.1", yaml: `name: coder_claim_within_5min description: | Если задача с лейблом for-coder висит в Todo больше 5 минут без assignee — генерируем warning-алерт. severity: warning trigger: event: label_set label: for-coder task_state: Todo condition: age_min: 5 has_assignee: false action: - notify: telegram - mark: ai_inactive`, }, { name: "reviewer_pickup_within_1h", description: "Reviewer должен взять задачу с for-reviewer в течение 60 минут.", severity: "info", active: true, triggers24h: 1, lastTriggered: "2026-05-17T12:14:02Z", role: "reviewer", claudeMdRef: "agents/reviewer/CLAUDE.md § 2.1", yaml: `name: reviewer_pickup_within_1h severity: info trigger: event: label_set label: for-reviewer condition: age_min: 60 has_reviewer_assignee: false`, }, { name: "stuck_in_progress_3h", description: "Задача >3ч в In Progress без новых коммитов или комментариев.", severity: "warning", active: true, triggers24h: 2, lastTriggered: "2026-05-17T13:51:44Z", role: "coder", claudeMdRef: "agents/coder/CLAUDE.md § 4.3", yaml: `name: stuck_in_progress_3h severity: warning trigger: event: poll condition: state: In Progress age_min: 180 no_activity: true`, }, { name: "watcher_heartbeat_missing", description: "Watcher агента не отвечает >5 минут — вероятный сбой сессии.", severity: "critical", active: true, triggers24h: 1, lastTriggered: "2026-05-17T14:08:11Z", role: "any", claudeMdRef: "agents//CLAUDE.md § 1.2", yaml: `name: watcher_heartbeat_missing severity: critical trigger: event: heartbeat_lost condition: threshold_min: 5 action: - notify: telegram - escalate: viktor`, }, { name: "labels_conflict", description: "На задаче >1 лейбла for- одновременно.", severity: "warning", active: true, triggers24h: 1, lastTriggered: "2026-05-17T10:42:18Z", role: "architect", claudeMdRef: "agents/architect/CLAUDE.md § 5", yaml: `name: labels_conflict severity: warning trigger: event: labels_changed condition: for_role_labels_count: ">1"`, }, { name: "task_no_assignee_2h", description: "Задача в Todo >2ч и ни у кого не назначена.", severity: "warning", active: false, triggers24h: 0, lastTriggered: "2026-05-15T09:42:00Z", role: "any", claudeMdRef: "agents/architect/CLAUDE.md § 2", yaml: `name: task_no_assignee_2h severity: warning trigger: event: poll condition: state: Todo age_min: 120 has_assignee: false`, }, { name: "context_below_20pct", description: "Самооценка контекста агента < 20% — риск сбоя.", severity: "info", active: true, triggers24h: 2, lastTriggered: "2026-05-17T14:01:00Z", role: "any", claudeMdRef: "agents//CLAUDE.md § 1.4", yaml: `name: context_below_20pct severity: info trigger: event: agent_heartbeat condition: context_remaining_pct: "<20"`, }, ]; // --------------------------------------------------------------------------- // Tasks history // --------------------------------------------------------------------------- // Generate ~40 tasks over the last ~14 days const TASKS = [ { seq: 12, title: "Скелет проекта Next.js", module: "scaffold", state: "Done", status: "ok", createdAt: "2026-05-03T09:14:00Z", closedAt: "2026-05-03T15:22:00Z", agents: ["lev","kostya","roma"] }, { seq: 13, title: "Базовая модель данных Order", module: "orders", state: "Done", status: "ok", createdAt: "2026-05-04T08:02:00Z", closedAt: "2026-05-04T18:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 14, title: "FastAPI: эндпоинт /orders", module: "orders", state: "Done", status: "warn", createdAt: "2026-05-04T11:40:00Z", closedAt: "2026-05-05T14:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 15, title: "TG-бот: команда /start", module: "tg-bot", state: "Done", status: "ok", createdAt: "2026-05-05T09:00:00Z", closedAt: "2026-05-05T13:11:00Z", agents: ["lev","kostya"] }, { seq: 16, title: "TG-бот: главное меню", module: "tg-bot", state: "Done", status: "ok", createdAt: "2026-05-05T14:00:00Z", closedAt: "2026-05-06T10:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 17, title: "KALK: первичный расчёт визиток", module: "kalk", state: "Done", status: "ok", createdAt: "2026-05-06T11:00:00Z", closedAt: "2026-05-07T09:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 18, title: "KALK: каталог форматов А5/А6", module: "kalk", state: "Done", status: "warn", createdAt: "2026-05-07T10:00:00Z", closedAt: "2026-05-08T17:00:00Z", agents: ["lev","kostya","timur","roma"] }, { seq: 19, title: "Авторизация: страница /login", module: "auth", state: "Done", status: "ok", createdAt: "2026-05-08T09:30:00Z", closedAt: "2026-05-09T11:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 20, title: "Авторизация: JWT + рефреш", module: "auth", state: "Done", status: "crit", createdAt: "2026-05-09T12:00:00Z", closedAt: "2026-05-10T22:00:00Z", agents: ["lev","kostya","roma","timur"] }, { seq: 21, title: "Документация: README первая ревизия", module: "docs", state: "Done", status: "ok", createdAt: "2026-05-10T09:00:00Z", closedAt: "2026-05-10T13:00:00Z", agents: ["lev","semyon"] }, { seq: 22, title: "TG-бот: пагинация каталога", module: "tg-bot", state: "Done", status: "ok", createdAt: "2026-05-10T14:00:00Z", closedAt: "2026-05-11T11:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 23, title: "KALK: справочник тиражей", module: "kalk", state: "Done", status: "ok", createdAt: "2026-05-11T11:30:00Z", closedAt: "2026-05-11T17:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 24, title: "Каталог: SEO meta-теги", module: "seo", state: "Done", status: "warn", createdAt: "2026-05-11T18:00:00Z", closedAt: "2026-05-12T20:00:00Z", agents: ["lev","kostya","semyon","roma"] }, { seq: 25, title: "Корзина: добавление позиций", module: "orders", state: "Done", status: "ok", createdAt: "2026-05-12T09:00:00Z", closedAt: "2026-05-12T18:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 26, title: "TG-бот: создание заказа", module: "tg-bot", state: "Done", status: "crit", createdAt: "2026-05-12T13:00:00Z", closedAt: "2026-05-14T16:00:00Z", agents: ["lev","kostya","roma","timur"] }, { seq: 27, title: "Аналитика: дашборд продаж", module: "orders", state: "Done", status: "ok", createdAt: "2026-05-13T09:00:00Z", closedAt: "2026-05-13T19:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 28, title: "Каталог: фильтры по материалу", module: "orders", state: "Done", status: "ok", createdAt: "2026-05-13T11:00:00Z", closedAt: "2026-05-14T10:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 29, title: "TG-бот: статусы заказа", module: "tg-bot", state: "Done", status: "ok", createdAt: "2026-05-14T11:00:00Z", closedAt: "2026-05-14T17:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 30, title: "KALK: расчёт листовок", module: "kalk", state: "Done", status: "warn", createdAt: "2026-05-14T12:00:00Z", closedAt: "2026-05-15T11:00:00Z", agents: ["lev","kostya","timur","roma"] }, { seq: 31, title: "Каталог: SEO sitemap.xml", module: "seo", state: "Done", status: "ok", createdAt: "2026-05-15T09:00:00Z", closedAt: "2026-05-15T14:00:00Z", agents: ["lev","kostya","semyon","roma"] }, { seq: 32, title: "Авторизация: восстановление пароля", module: "auth", state: "Done", status: "ok", createdAt: "2026-05-15T10:00:00Z", closedAt: "2026-05-15T18:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 33, title: "TG-бот: оплата картой", module: "tg-bot", state: "Done", status: "warn", createdAt: "2026-05-15T11:00:00Z", closedAt: "2026-05-16T14:00:00Z", agents: ["lev","kostya","roma","timur"] }, { seq: 34, title: "KALK: афиши, плакаты", module: "kalk", state: "Done", status: "ok", createdAt: "2026-05-15T13:00:00Z", closedAt: "2026-05-16T12:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 35, title: "Каталог: ленты блога", module: "seo", state: "Done", status: "ok", createdAt: "2026-05-15T14:00:00Z", closedAt: "2026-05-16T15:00:00Z", agents: ["lev","semyon","kostya","roma"] }, { seq: 36, title: "TG-бот: уведомления", module: "tg-bot", state: "Done", status: "ok", createdAt: "2026-05-15T16:00:00Z", closedAt: "2026-05-16T17:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 37, title: "Авторизация: 2FA", module: "auth", state: "Done", status: "crit", createdAt: "2026-05-16T08:30:00Z", closedAt: "2026-05-17T03:00:00Z", agents: ["lev","kostya","roma","timur"] }, { seq: 38, title: "KALK: расчёт буклетов", module: "kalk", state: "Done", status: "ok", createdAt: "2026-05-16T09:00:00Z", closedAt: "2026-05-16T18:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 39, title: "Каталог: интеграция с Yandex feed", module: "seo", state: "Done", status: "warn", createdAt: "2026-05-16T10:00:00Z", closedAt: "2026-05-17T09:00:00Z", agents: ["lev","kostya","semyon","roma"] }, { seq: 40, title: "TG-бот: история заказов клиента", module: "tg-bot", state: "Done", status: "ok", createdAt: "2026-05-16T11:00:00Z", closedAt: "2026-05-17T08:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 41, title: "KALK: тиражи > 1000", module: "kalk", state: "Done", status: "ok", createdAt: "2026-05-16T12:00:00Z", closedAt: "2026-05-17T10:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 42, title: "Аналитика: воронка конверсии", module: "orders", state: "Done", status: "ok", createdAt: "2026-05-16T13:00:00Z", closedAt: "2026-05-17T11:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 43, title: "Каталог: страница «о мастерской»", module: "seo", state: "Done", status: "ok", createdAt: "2026-05-16T14:00:00Z", closedAt: "2026-05-17T09:30:00Z", agents: ["lev","semyon","roma"] }, { seq: 44, title: "Telegram-бот: подсчёт стоимости", module: "tg-bot", state: "Done", status: "warn", createdAt: "2026-05-17T09:00:00Z", closedAt: "2026-05-17T13:00:00Z", agents: ["lev","kostya","roma"] }, { seq: 45, title: "Onboarding wizard для KALK", module: "kalk", state: "Done", status: "ok", createdAt: "2026-05-17T08:00:00Z", closedAt: "2026-05-17T11:31:00Z", agents: ["lev","kostya","roma"] }, { seq: 46, title: "Code review: миграция БД на v3", module: "orders", state: "Done", status: "info", createdAt: "2026-05-17T10:00:00Z", closedAt: "2026-05-17T13:58:00Z", agents: ["lev","kostya","roma"] }, { seq: 47, title: "Аналитика заказов: фильтр по статусу", module: "orders", state: "In Progress", status: "warn", createdAt: "2026-05-17T10:39:00Z", closedAt: null, agents: ["lev","kostya"] }, { seq: 48, title: "SEO: meta-теги для каталога", module: "seo", state: "Done", status: "ok", createdAt: "2026-05-17T12:00:00Z", closedAt: "2026-05-17T13:31:00Z", agents: ["lev","semyon","roma"] }, { seq: 49, title: "Ребрендинг Print-X в коде сайта", module: "seo", state: "In Progress", status: "warn", createdAt: "2026-05-17T14:00:00Z", closedAt: null, agents: ["lev","kostya"] }, { seq: 50, title: "Подобрать референсы для каталога A4", module: "seo", state: "In Progress", status: "crit", createdAt: "2026-05-17T13:30:00Z", closedAt: null, agents: ["semyon"] }, { seq: 51, title: "Описать модуль уведомлений в KALK", module: "kalk", state: "In Progress", status: "open", createdAt: "2026-05-17T14:31:00Z", closedAt: null, agents: ["lev"] }, { seq: 52, title: "Разбор алертов за смену", module: "watchdog", state: "In Progress", status: "open", createdAt: "2026-05-17T12:54:00Z", closedAt: null, agents: ["viktor"] }, ]; // --------------------------------------------------------------------------- // Agents CLAUDE.md (markdown-like) // --------------------------------------------------------------------------- const AGENT_DOCS = { architect: `# CLAUDE.md — Architect (Лев) ## 1. Роль Ты — главный архитектор команды. Ты создаёшь задачи в Plane на основании ТЗ от Максима, разбиваешь крупные эпики на атомарные задачи и распределяешь их по ролям через лейблы \`for-\`. ## 2. Создание задач Каждая новая задача обязана иметь: - внятный заголовок (≤ 60 символов) - описание с критериями приёмки - ровно один лейбл \`for-\` - модуль (тег: \`orders\`, \`kalk\`, \`tg-bot\`, ...) ## 3. Декомпозиция - Не создавай задач, оценка которых > 4 часов работы агента. - Если задача больше — разбей на под-задачи и свяжи их через relation. ## 4. Качество ТЗ - Каждая задача должна включать секцию «Definition of Done». - Если описание спорное — попроси Максима уточнить через Plane-комментарий. ## 5. Лейблы В любой момент на задаче должен быть ровно один лейбл \`for-\`. Перед сменой роли убери предыдущий лейбл. ## 6. Эскалация Если задача висит в Backlog > 24 часов — поставь лейбл \`needs-triage\` и позови Виктора через коммент. `, coder: `# CLAUDE.md — Coder (Костя) ## 1. Роль Ты — программист. Ты пишешь код по задачам в Plane. Стек: Next.js 14, FastAPI, PostgreSQL, Python 3.12. ## 2. Подбор задач Регулярно проверяй задачи в state \`Todo\` с лейблом \`for-coder\`. ### 3.1 SLA на подбор Если задача помечена \`for-coder\` и её state = Todo, ты обязан забрать её (assign на себя + перевести в In Progress) в течение 5 минут. Если занят — оставь комментарий \`busy, eta NN min\`. ## 4. Работа - Делай коммит после каждой логической единицы. - Не пиши код без тестов (хотя бы один интеграционный). ### 4.3 Прогресс по задаче Если задача в In Progress дольше 2 часов без коммита — оставь промежуточный коммент \`progress: <что сделано>, <что осталось>\`. Если застрял — переведи в Blocked. ## 5. Передача в ревью - Перед сменой лейбла на \`for-reviewer\` запусти \`pytest && pnpm test\`. - В коммент пиши: \`ready for review @roma: <ссылка на PR>\`. ## 6. Контекст Если контекст < 20% — переведи задачу в Blocked и попроси перезапуск. `, reviewer: `# CLAUDE.md — Reviewer (Рома) ## 1. Роль Ты — ревизор кода. Ты проверяешь PR, оставленные Костей, на соответствие архитектуре и стандартам. ### 2.1 SLA на ревью Задачу с лейблом \`for-reviewer\` необходимо взять в ревью в течение 1 часа от установки лейбла. ## 3. Чек-лист - Все тесты зелёные? - Code style: Black + Ruff + Prettier. - Нет копипасты > 8 строк. - DTO/типы согласованы между фронтом и бэком. - Безопасность: проверь SQL-инъекции, XSS, авторизацию. ## 4. Решение - Если всё ок — approve и переведи в \`Done\`. - Если есть замечания — оставь коммент с пунктами и верни в \`In Progress\` с лейблом \`for-coder\`. ## 5. Эскалация Если PR требует архитектурного решения — добавь \`for-architect\` и сними \`for-reviewer\`. `, tester: `# CLAUDE.md — Tester (Тимур) ## 1. Роль Ты — тестировщик. Пишешь E2E (Playwright) для сценариев из задачи и ad-hoc проверяешь edge-cases. ## 2. Подбор задач Подключайся к задачам после approve ревизора — добавляй сценарии в \`tests/e2e/\`. ## 3. Отчёт - Если сценарий пройден — поставь лейбл \`tested\`. - Если найден баг — заводи отдельный bug-репорт со ссылкой на PR. ## 4. Покрытие Минимум один happy-path и один edge-case на каждую новую фичу. `, scout: `# CLAUDE.md — Scout (Семён) ## 1. Роль Ты — контент-разведчик. Ищешь референсы, SEO-материал, конкурентов полиграфической мастерской «Мульти принт» по А4/A5/визиткам. ### 1.2 Жизненный цикл сессии Сессия scout-агента должна работать непрерывно, отправляя heartbeat в watcher раз в 30 секунд. Если сессия завершилась — перезапусти её через \`wt agent restart scout\`. ## 2. Источники - Yandex, Google (SerpAPI). - Pinterest, Behance — для визуальных референсов. - Конкуренты: printio, vistaprint, GoodPrint. ## 3. Формат отчёта В коммент к задаче — markdown-список из 5–10 пунктов с ссылками и кратким резюме. ## 4. Качество Никогда не копируй текст конкурента дословно. Только пересказ + ссылка. `, "workflow-lead": `# CLAUDE.md — Workflow Lead (Виктор) ## 1. Роль Ты — главный над процессом. Ты разбираешь алерты watchdog'а и вмешиваешься в работу команды, если что-то идёт не так. ## 2. Источники сигналов - Telegram-нотификации от WorkflowTools-TgBot. - Дашборд WorkflowTools-Watchdog (http://lan:8080). - Прямые задачи от Максима. ## 3. Действия по алерту - Прочти описание, открой задачу в Plane. - Прочти CLAUDE.md соответствующей роли (раздел из алерта). - Реши: пинговать агента, переназначить, перезапустить watcher. - Закрой алерт (dismiss + комментарий). ## 4. Эскалация Если алерт повторяется >2 раз в сутки — заведи задачу «Уточнить правило » с лейблом \`for-architect\`. `, }; // --------------------------------------------------------------------------- // Util / formatters // --------------------------------------------------------------------------- function fmtTime(ts) { const d = new Date(ts); return d.toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" }); } function fmtDate(ts) { const d = new Date(ts); return d.toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit" }); } function fmtDateTime(ts) { return `${fmtDate(ts)} ${fmtTime(ts)}`; } function fmtAgo(ts) { const now = NOW.getTime(); const t = new Date(ts).getTime(); const diffSec = Math.max(0, Math.round((now - t) / 1000)); if (diffSec < 60) return `${diffSec}с назад`; const m = Math.round(diffSec / 60); if (m < 60) return `${m} мин назад`; const h = Math.floor(m / 60); const mm = m % 60; if (h < 24) return `${h}ч ${mm}м назад`; const d = Math.floor(h / 24); return `${d}д назад`; } function fmtDuration(min) { if (min < 60) return `${min} мин`; const h = Math.floor(min / 60); const m = min % 60; return m ? `${h}ч ${m}м` : `${h}ч`; } function severityToTone(s) { if (s === "critical") return "crit"; if (s === "warning") return "warn"; if (s === "info") return "info"; return "neutral"; } // Counts const ACTIVE_ALERTS = ALERTS.filter(a => a.status === "active"); const CRITICAL_COUNT = ACTIVE_ALERTS.filter(a => a.severity === "critical").length; const WARNING_COUNT = ACTIVE_ALERTS.filter(a => a.severity === "warning").length; Object.assign(window, { NOW, TEAM, teamById, ALERTS, EVENTS, RULES, TASKS, AGENT_DOCS, ACTIVE_ALERTS, CRITICAL_COUNT, WARNING_COUNT, fmtTime, fmtDate, fmtDateTime, fmtAgo, fmtDuration, severityToTone, });