# ТЗ — ORCH-067: Telegram tracker (bump + статусы Plane + кликабельный номер задачи) Work Item: **ORCH-067** Документ описывает КОНКРЕТНЫЕ изменения кода/конфигурации/тестов и документации. Архитектурные развилки помечены `[ARCH]` — решение принимает архитектор (ADR), здесь зафиксированы только требования и ограничения к ним. --- ## 0. Задействованные модули `src/` | Модуль | Роль в задаче | |---|---| | `src/config.py` | Дефолт `tracker_mode`; поле `plane_web_url`/`plane_workspace_slug` (уже есть). | | `src/notifications.py` | Основные изменения: bump-дефолт, статус-строка карточки, хелпер ссылки, применение хелпера в `notify_*`. | | `src/plane_sync.py` | Источник имён статусов/маппинга ORCH-066 (`_PLANE_NAME_TO_KEY`, `_STAGE_TO_STATE_KEY`); при необходимости reverse-map UUID→имя `[ARCH]`. | | `src/projects.py` | `get_project_by_repo(repo).plane_project_id` — per-task project_id для ссылки. | | `src/db.py` | Чтение `tasks.plane_issue_id`, `tasks.repo` (без изменений схемы). | | `src/stage_engine.py`, `src/agents/launcher.py`, `src/merge_gate.py`, `src/job_reaper.py`, `src/security_gate.py`, `src/reconciler.py`, `src/main.py` | Точки `send_telegram`, где есть `work_item_id` — применить хелпер ссылки (требование 4). | Изменения API (HTTP endpoints) — **нет**. Изменения схемы БД — **нет**. Новые QG checks — **нет**. --- ## 1. Требование 1 — bump по умолчанию ### 1.1. Изменение - `src/config.py` (~стр. 408): сменить дефолт `tracker_mode: str = "edit"` → `tracker_mode: str = "bump"`. - Обновить docstring-комментарий рядом (ORCH-042): отметить, что **дефолт теперь `bump`**, `edit` остаётся доступен через `ORCH_TRACKER_MODE=edit`. ### 1.2. Без изменений (сохранить инвариант) - Логика `update_task_tracker` (`src/notifications.py`, ветка `if mode == "bump"`): `delete_telegram(old)` best-effort → `send_telegram(text, disable_notification=True)` → `set_tracker_message_id` ТОЛЬКО при `new_mid is not None`. Не менять. - `send_telegram`/`edit_telegram`/`delete_telegram` — не трогать. ### 1.3. Прод-аспект - Для прод-инстанса орка можно дополнительно выставить `ORCH_TRACKER_MODE=bump` в `.env` на хосте (как страховку), но код должен работать «из коробки» и без env. Канон env — `.env.example` (обновить, если там фигурирует tracker_mode). --- ## 2. Требование 2 — статус-строка карточки по модели ORCH-066 ### 2.1. Новый чистый хелпер маппинга Добавить в `src/notifications.py` функцию, возвращающую отображаемый Plane-статус для карточки на основе доступных данных задачи. Сигнатура (ориентир): ```python def plane_status_label(task_row) -> str: """Вернуть строку текущего Plane-статуса для шапки карточки (с emoji). Никогда не падает: на неизвестном входе -> разумный дефолт по stage.""" ``` Хелпер обязан быть чистым/детерминированным от входных данных и **никогда не бросать** исключения (любая ошибка → дефолт по `stage`, рендер карточки не ломается). ### 2.2. Маппинг внутреннее состояние → Plane-статус (обязательные строки) Имена статусов — финальные из модели ORCH-066 (см. `_PLANE_NAME_TO_KEY` в `plane_sync.py`). | Источник (данные задачи в БД) | Plane-статус (отображение в карточке) | |---|---| | `stage == "created"` | `To Analyse` | | `stage == "analysis"`, BRD-clock не запущен | `Analysis` | | `stage == "analysis"`, `brd_review_started_at` есть, `brd_review_ended_at` пуст | `⏸️ In Review — ожидание согласования BRD` | | `stage == "architecture"` | `Architecture` | | `stage == "development"` | `Development` | | `stage == "review"` | `Code-Review` | | `stage == "testing"` | `Testing` | | `stage == "deploy"` (ожидание Confirm Deploy) | `⏸️ Awaiting Deploy — ожидание Confirm Deploy` | | `stage == "done"` | `Done` | Ветки (Needs Input / Blocked / Rejected / Cancelled / Deploying / Monitoring after Deploy): - `❓ Needs Input — нужны уточнения` — состояние «аналитик задал вопросы»; - `Blocked`, `Rejected`, `Cancelled`, `Deploying`, `Monitoring after Deploy`. `[ARCH]` **Источник сигнала для веток, не выводимых из `tasks.stage`** (Needs Input, Blocked, Rejected, Cancelled, Deploying, Monitoring after Deploy): - запрещено менять схему БД (нельзя добавлять колонку-флаг); - варианты для архитектора: (а) best-effort чтение живого Plane-статуса (`fetch_issue_state` + reverse-map UUID→имя через `get_project_states`/ `_PLANE_NAME_TO_KEY`) с обязательным fail-safe (нет сети/ответа → деградация на stage-маппинг, без задержки, блокирующей конвейер); (б) только stage-выводимые статусы, а ветки — по уже имеющимся сигналам (например, In Review через brd-clock). - ОБЯЗАТЕЛЬНО к покрытию (DoD): `⏸️ In Review`, `⏸️ Awaiting Deploy`, `❓ Needs Input`. In Review полностью выводится из brd-clock (см. таблицу) и должен работать без сети. ### 2.3. Встраивание в `render_task_tracker` - В `render_task_tracker` (`src/notifications.py`) добавить в шапку/верх карточки отдельную СТРОКУ статуса (под заголовком `🛠️ ORCH-NNN · ` / над разделителем `bar`), напр.: `📍 <status_label>`. - Существующие строки по стадиям (`✅ done` / `🔄 active`), строка «Подтверждение BRD», тоталы токенов/стоимости, done-строка с PR/⏱️ — СОХРАНИТЬ (семантику не ломать). - Семантика строки «Подтверждение BRD» (⏸️+⏳ при ожидании, ✅ при пройденном гейте) сохраняется; новая статус-строка дублирует её смысл в терминах Plane-статуса. --- ## 3. Требование 3 + 4 — кликабельный номер задачи ### 3.1. Единый хелпер Добавить в `src/notifications.py`: ```python def plane_issue_link(work_item_id, plane_issue_id=None, project_id=None, repo=None) -> str: """Вернуть HTML с кликабельным номером задачи (<a href=...>ORCH-NNN</a>), либо просто html.escape(work_item_id), если ссылку построить нельзя. Никогда не падает.""" ``` Поведение: - База URL: `settings.plane_web_url` → fallback `settings.plane_api_url`; loopback-база (`localhost`/`127.0.0.1`/…) трактуется как «нет web URL» (переиспользовать `_is_loopback_base`). - `workspace_slug`: `settings.plane_workspace_slug`. - `project_id`: явный аргумент → иначе резолв по `repo` через `get_project_by_repo(repo).plane_project_id`. - `issue_id`: `plane_issue_id` (UUID из `tasks.plane_issue_id`). - URL-шаблон: `{web_base}/{workspace}/projects/{project_id}/issues/{issue_id}/`. - Текст ссылки = `html.escape(work_item_id)`; `href` = `html.escape(url, quote=True)`. - **Fail-safe:** если не хватает любого из (`web_base` валидный/не loopback, `workspace`, `project_id`, `plane_issue_id`) → вернуть `html.escape(work_item_id)` (номер без ссылки). - Логика построения URL уже существует в `_build_plane_issue_link` (ORCH-017) — допустимо переиспользовать/обобщить её, разнеся «текст-ссылки = номер» и «текст-ссылки = `✅ Задача в Plane`», чтобы не дублировать резолв проекта и loopback-guard. ### 3.2. Применение в карточке (требование 3) - В `render_task_tracker` заголовок строится из `work_item_id`. Заменить `html.escape(work_item_id)` в обоих вариантах заголовка (done / not-done) на `plane_issue_link(work_item_id, plane_issue_id, repo=repo)` — номер становится кликабельным. - Для этого `render_task_tracker` должен дополнительно выбрать из БД `repo` и `plane_issue_id` (расширить существующий `SELECT` по `tasks`). Схему НЕ менять — колонки уже есть. - `title` уже экранируется (`html.escape(title)`) — сохранить. ### 3.3. Применение во всех уведомлениях (требование 4) Во всех точках `send_telegram`/`notify_*`, где в тексте есть `work_item_id`, заменить «сырой» номер на `plane_issue_link(...)`. Перечень точек (из `src`): - `src/notifications.py`: `notify_approve_requested`, `notify_error` (и любые будущие notify_* с work_item_id); - `src/stage_engine.py`: все `send_telegram(...)` с `work_item_id` (≈ строки 613, 672, 719, 776, 820, 916, 971, 1057, 1134, 1192, 1228, 1257, 1355, 1367, 1425, 1447, 1601 — проверить каждую: применять ТОЛЬКО где упоминается номер задачи); - `src/agents/launcher.py`: deploy-failed alert (≈685–686), agent-failed alert (≈698–699), alert ≈821–822; - `src/merge_gate.py` (≈431–432); - `src/job_reaper.py` (≈395–396); - `src/security_gate.py` (≈673–674); - `src/reconciler.py` (≈449); - `src/main.py` (≈45–47). `[ARCH]` Способ доступа к `plane_issue_id`/`project_id` в каждой точке (часто там уже есть `work_item_id`, но не обязательно `plane_issue_id`): хелпер должен уметь резолвить недостающее по `repo`/БД, оставаясь fail-safe. Допустимо добавить тонкую обёртку, которая по `work_item_id`/`task_id` достаёт `repo`+`plane_issue_id` из БД и зовёт `plane_issue_link` (аналогично существующему `_get_task_link_fields`). Везде, где данных нет — деградация на просто номер, без падения. ### 3.4. HTML-экранирование - `parse_mode=HTML` уже стоит в `send_telegram`/`edit_telegram`. Любой пользовательский текст (title, описания, причины QG-fail, сообщения об ошибках), попадающий в сообщение с ссылками, должен экранироваться `html.escape`, чтобы не сломать `<a>`-разметку. --- ## 4. Конфигурация - `plane_web_url` (env `ORCH_PLANE_WEB_URL`) — уже существует (`src/config.py`), значение прод — `plane.mva154.duckdns.org` (схему `https://` учесть при сборке URL). Дополнительных полей конфигурации не требуется. - `tracker_mode` — сменить дефолт на `bump` (раздел 1). - Обновить `.env.example`, если в нём фигурируют `ORCH_TRACKER_MODE` / `ORCH_PLANE_WEB_URL` (канон секретов/настроек — `.env.example`, не коммитить реальные секреты). --- ## 5. Артефакты pipeline, которые должны быть созданы/обновлены - `docs/work-items/ORCH-067/06-adr/ADR-NNN-*.md` — архитектурное решение (минимум: источник «истинного» Plane-статуса для веток при запрете изменения схемы БД; дефолт bump; единый хелпер ссылки). - `CLAUDE.md` — раздел про нотификации/tracker (дефолт bump; статус-строка карточки; кликабельный номер в карточке и уведомлениях). - `CHANGELOG.md` — запись ORCH-067. - `docs/architecture/README.md` — при необходимости синхронизировать описание tracker'а. --- ## 6. Ограничения (что НЕ трогать) - Транспорт `send_telegram`/`edit_telegram`/`delete_telegram`. - Инвариант «одна карточка на задачу». - Логику `disable_notification` (карточка тихая; пингуют только alert-хелперы). - `STAGE_TRANSITIONS`, Quality Gates, схему БД. - Поведение агентов/конвейера. --- ## 7. Замечания по самохостингу Орк правит сам себя в проде (общий инстанс/БД с enduro-trails): - НЕ перезапускать прод-контейнер `orchestrator` в рамках задачи. - Обязательная страховка через `deploy-staging` (8501) до прод-деплоя. - Смена дефолта `tracker_mode` затрагивает ВСЕ проекты — проверить отсутствие регресса для enduro-trails (тесты + staging-наблюдение карточки).