From 180f8dc54b62912c32a087a36a895799c0d3519e Mon Sep 17 00:00:00 2001 From: Stream Date: Thu, 21 May 2026 21:10:01 +0300 Subject: [PATCH] auto-sync: 2026-05-21 21:10:01 --- memory/2026-05-21.md | 19 ++ tasks/multi-agent/DEV_TASK_PLANE_SYNC.md | 231 +++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 tasks/multi-agent/DEV_TASK_PLANE_SYNC.md diff --git a/memory/2026-05-21.md b/memory/2026-05-21.md index 4795710..2bdc7b2 100644 --- a/memory/2026-05-21.md +++ b/memory/2026-05-21.md @@ -52,6 +52,25 @@ analysis → architecture → development → review → testing → deploy → - ✅ Workspace `/home/node/.openclaw/workspace-analyst/` - ✅ Уже делал задачи (ET-002 артефакты) +## Dev-агент: фикс критических багов (17:38-17:52) + +Dev-агент (run `624ca711`) выполнил все 5 фиксов: +1. ✅ git уже был в Dockerfile +2. ✅ GIT_AUTHOR_NAME/EMAIL = claude-bot в launcher.py +3. ✅ task_id передаётся в launch() и записывается в БД +4. ✅ Timeout watchdog (30 мин, kill -9) +5. ✅ Auto-advance в gitea.py: CI green → review, PR approved → testing, back-to:dev при request_changes + +Rebuild успешен: git v2.47.3 в контейнере, health OK. + +## Dev-агент: Plane sync (18:00+) + +Запущен Dev-агент (run `bad29612`) на добавление обратной связи Orchestrator → Plane: +- Новый модуль `src/plane_sync.py` +- Обновление state issue при смене stage +- Комментарии в Plane при каждом переходе +- Интеграция в webhooks/plane.py и webhooks/gitea.py + ### Plane - Проект "Enduro Trails" (identifier: ET) diff --git a/tasks/multi-agent/DEV_TASK_PLANE_SYNC.md b/tasks/multi-agent/DEV_TASK_PLANE_SYNC.md new file mode 100644 index 0000000..4217cce --- /dev/null +++ b/tasks/multi-agent/DEV_TASK_PLANE_SYNC.md @@ -0,0 +1,231 @@ +# DEV_TASK_PLANE_SYNC.md — Обратная связь Orchestrator → Plane + +--- +type: dev-task +priority: high +project: multi-agent +created_at: 2026-05-21 +author: stream +--- + +## Контекст + +Orchestrator получает webhooks из Plane, но не пишет обратно. Слава не видит прогресс задач в Plane. Нужно добавить sync: при смене stage → обновлять state issue, добавлять комментарии. + +## Plane API + +**Base URL:** `http://localhost:8091/api/v1` +**Auth:** Header `X-API-Key: ` (уже в .env) +**Workspace:** `ag_proj` +**Project ID:** `7a79f0a9-5278-49cd-9007-9a338f238f9c` + +### States (project "Enduro Trails") + +| ID | Name | Group | +|----|------|-------| +| `113b24f6-cce8-4be9-9a22-a359b9cf0122` | Backlog | backlog | +| `2c7d3df3-9eb9-419b-92b7-d7d560bcdd10` | Todo | unstarted | +| `b873d9eb-993c-48cd-97ac-99a9b1623967` | In Progress | started | +| `381a2833-3c4e-4be5-bd0f-be84cb946ad8` | Done | completed | +| `b1cae7f9-961d-4889-a179-f3acea697d17` | Cancelled | cancelled | + +### API Endpoints + +**Update issue state:** +``` +PATCH /workspaces/ag_proj/projects/{project_id}/issues/{issue_id}/ +Body: {"state": ""} +``` + +**Add comment:** +``` +POST /workspaces/ag_proj/projects/{project_id}/issues/{issue_id}/comments/ +Body: {"comment_html": "

text

"} +``` + +**Get issue by identifier (sequence_id):** +``` +GET /workspaces/ag_proj/projects/{project_id}/issues/?search=ET-002 +``` + +## Задачи + +### 1. Создать модуль `src/plane_sync.py` + +```python +"""Plane API sync — update issue state and add comments.""" + +import logging +import httpx +from .config import settings + +logger = logging.getLogger("orchestrator.plane_sync") + +PLANE_BASE = f"{settings.plane_api_url}/api/v1" +PLANE_HEADERS = {"X-API-Key": settings.plane_api_token} +WORKSPACE = settings.plane_workspace_slug +PROJECT_ID = "7a79f0a9-5278-49cd-9007-9a338f238f9c" + +# Map orchestrator stages to Plane states +STAGE_TO_STATE = { + "created": "2c7d3df3-9eb9-419b-92b7-d7d560bcdd10", # Todo + "analysis": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress + "architecture": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress + "development": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress + "review": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress + "testing": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress + "deploy": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress + "done": "381a2833-3c4e-4be5-bd0f-be84cb946ad8", # Done +} + + +def find_issue_id(work_item_id: str) -> str | None: + """Find Plane issue UUID by work_item_id (e.g. 'ET-002').""" + url = f"{PLANE_BASE}/workspaces/{WORKSPACE}/projects/{PROJECT_ID}/issues/" + try: + resp = httpx.get(url, headers=PLANE_HEADERS, params={"search": work_item_id}, timeout=10) + resp.raise_for_status() + data = resp.json() + results = data.get("results", data if isinstance(data, list) else []) + for issue in results: + # Match by sequence_id or name containing work_item_id + seq = issue.get("sequence_id") + identifier = f"ET-{seq}" if seq else "" + if identifier == work_item_id or work_item_id in issue.get("name", ""): + return issue["id"] + except Exception as e: + logger.error(f"Failed to find issue for {work_item_id}: {e}") + return None + + +def update_issue_state(work_item_id: str, stage: str): + """Update Plane issue state based on orchestrator stage.""" + state_id = STAGE_TO_STATE.get(stage) + if not state_id: + return + + issue_id = find_issue_id(work_item_id) + if not issue_id: + logger.warning(f"Issue not found in Plane for {work_item_id}") + return + + url = f"{PLANE_BASE}/workspaces/{WORKSPACE}/projects/{PROJECT_ID}/issues/{issue_id}/" + try: + resp = httpx.patch(url, headers=PLANE_HEADERS, json={"state": state_id}, timeout=10) + resp.raise_for_status() + logger.info(f"Plane: {work_item_id} state → {stage} ({state_id[:8]}...)") + except Exception as e: + logger.error(f"Failed to update Plane state for {work_item_id}: {e}") + + +def add_comment(work_item_id: str, text: str): + """Add a comment to Plane issue.""" + issue_id = find_issue_id(work_item_id) + if not issue_id: + logger.warning(f"Issue not found in Plane for {work_item_id}, skipping comment") + return + + url = f"{PLANE_BASE}/workspaces/{WORKSPACE}/projects/{PROJECT_ID}/issues/{issue_id}/comments/" + html = f"

{text}

" + try: + resp = httpx.post(url, headers=PLANE_HEADERS, json={"comment_html": html}, timeout=10) + resp.raise_for_status() + logger.info(f"Plane: comment added to {work_item_id}") + except Exception as e: + logger.error(f"Failed to add comment to {work_item_id}: {e}") + + +def notify_stage_change(work_item_id: str, old_stage: str, new_stage: str, agent: str = None): + """Notify Plane about stage transition.""" + update_issue_state(work_item_id, new_stage) + + msg = f"🔄 Stage: {old_stage} → {new_stage}" + if agent: + msg += f" (launching {agent})" + add_comment(work_item_id, msg) + + +def notify_qg_failure(work_item_id: str, stage: str, check: str, reason: str): + """Notify Plane about QG failure.""" + add_comment(work_item_id, f"⚠️ QG failed at {stage}: {check} — {reason}") + + +def notify_done(work_item_id: str): + """Mark issue as Done in Plane.""" + update_issue_state(work_item_id, "done") + add_comment(work_item_id, "✅ Task completed! PR merged and deployed.") +``` + +### 2. Интегрировать в webhooks/plane.py + +В функции `_try_advance_stage`, после `update_task_stage(task_id, next_stage)`: + +```python +from ..plane_sync import notify_stage_change as plane_notify_stage, notify_qg_failure as plane_notify_qg + +# После успешного advance: +plane_notify_stage(work_item_id, current_stage, next_stage, agent) + +# После QG failure: +plane_notify_qg(work_item_id, current_stage, qg_name, reason) +``` + +### 3. Интегрировать в webhooks/gitea.py + +В `handle_ci_status` и `handle_pr_review`, после advance: + +```python +from ..plane_sync import notify_stage_change as plane_notify_stage + +# После advance: +plane_notify_stage(work_item_id, current_stage, next_stage, agent) +``` + +### 4. Добавить config fields + +**Файл:** `/home/slin/repos/orchestrator/src/config.py` + +Добавить (если ещё нет): +```python +plane_api_url: str = os.getenv("ORCH_PLANE_API_URL", "http://localhost:8091") +plane_api_token: str = os.getenv("ORCH_PLANE_API_TOKEN", "") +plane_workspace_slug: str = os.getenv("ORCH_PLANE_WORKSPACE_SLUG", "ag_proj") +``` + +--- + +## Файлы для изменения + +- `/home/slin/repos/orchestrator/src/plane_sync.py` (НОВЫЙ) +- `/home/slin/repos/orchestrator/src/webhooks/plane.py` (добавить вызовы) +- `/home/slin/repos/orchestrator/src/webhooks/gitea.py` (добавить вызовы) +- `/home/slin/repos/orchestrator/src/config.py` (проверить что plane fields есть) + +## Ограничения + +- НЕ менять порт 8500 +- НЕ менять формат .env (все переменные уже есть) +- НЕ блокировать основной flow если Plane API недоступен (try/except, log error, continue) +- httpx уже в requirements.txt + +## Проверка + +```bash +# 1. Rebuild +cd /home/slin/repos/orchestrator && docker compose up -d --build + +# 2. Health +curl -s http://localhost:8500/health + +# 3. Smoke test — вызвать plane_sync напрямую +docker exec orchestrator python -c " +from src.plane_sync import find_issue_id, add_comment +issue_id = find_issue_id('ET-002') +print(f'Found issue: {issue_id}') +if issue_id: + add_comment('ET-002', '🧪 Test comment from Orchestrator') + print('Comment added!') +" + +# 4. Проверить в Plane UI что комментарий появился +```