auto-sync: 2026-06-03 10:00:01

This commit is contained in:
Stream
2026-06-03 10:00:01 +03:00
parent a91cf5c534
commit f0aa3b9c9e
3 changed files with 187 additions and 1 deletions

View File

@@ -0,0 +1,74 @@
# DEV TASK — финальная гигиена: L-1 (комментарий) + L-2 (ротация логов) + L-3 (эмодзи-константы)
**Проект:** orchestrator | **Сервер:** slin@82.22.50.71 | **Репо:** /home/slin/repos/orchestrator | **Контейнер:** orchestrator (8500)
**Ветка:** `feature/ORCH-cleanup-L1L2L3` из свежего main (git checkout main && git pull && git checkout -b feature/ORCH-cleanup-L1L2L3).
Три мелких независимых пункта, отдельными коммитами. Низкий риск, но аккуратность та же.
⚠️ **ГРАБЛЯ push (ORCH-7, НЕ повтори):** после push `git log origin/main..origin/feature/ORCH-cleanup-L1L2L3` ДОЛЖЕН показать твои коммиты ДО отчёта «PR готов».
---
## L-1 — поправить врущий комментарий в stages.py (1 строка)
**Где:** `src/stages.py:8` — в docstring/шапке `STAGE_TRANSITIONS`:
```
- agent: the agent to launch when entering the NEXT stage
```
**Проблема:** это НЕВЕРНО и именно этот нейминг породил баг ORCH-4 (launcher брал агента не той стадии). Строка 33 уже верная: «agent to launch when advancing FROM this stage».
**Фикс:** привести шапку (стр.8) в соответствие с фактической семантикой:
```
- agent: the agent to launch when advancing FROM this stage (NOT the next stage's agent)
```
Никакого кода не менять — только комментарий. STAGE_TRANSITIONS и логику НЕ трогать.
**Коммит:** `docs(stages): fix misleading STAGE_TRANSITIONS comment (L-1)`
---
## L-2 — ротация логов прогонов (реальная гигиена)
**Где:** логи пишутся в `/app/data/runs/{run_id}.log` (`src/agents/launcher.py:143` и др.). Накапливаются без ограничения.
**Что сделать:**
- Добавить функцию очистки старых логов в `runs/` — например `prune_run_logs(runs_dir, keep_days=30, keep_max=500)`: удаляет .log старше keep_days ИЛИ оставляет только keep_max самых свежих (что наступит раньше). Безопасно: только `*.log` в этом каталоге, не трогать другие файлы.
- Вызывать её в подходящий момент: либо при старте приложения (`init`/lifespan), либо после финализации job (`_finalize_job`). Выбери наименее инвазивный — вызов при старте проще и безопаснее. Best-effort: ошибка ротации НЕ должна ронять приложение (обернуть try/except + logger.warning).
- Параметры (keep_days/keep_max) — через env/config с разумными дефолтами (по образцу resilience-ключей в ORCH-1b, если такой паттерн есть; иначе константы с комментарием).
- НЕ удалять лог текущего/активного прогона.
**Тест:** `tests/test_log_rotation.py` — создать tmp-каталог с N фейковыми .log разного возраста (touch с mtime), вызвать prune, проверить что старые/лишние удалены, свежие и не-.log файлы целы.
**Коммит:** `feat(launcher): prune old run logs (L-2)`
---
## L-3 — эмодзи-литералы → константы (косметика)
**Где:** `src/plane_sync.py` — захардкоженные эмодзи в строках:
- стр.197 `f"🔄 Stage: {old_stage} → {new_stage}"`
- стр.235 `f"⚠️ QG failed at {stage}: ..."`
- стр.242 `"✅ Task completed! ..."`
**Что сделать:** вынести эмодзи в именованные константы в начало модуля (или в маленький общий модуль `src/emoji.py` если хочешь переиспользовать), например:
```python
EMOJI_STAGE = "🔄"
EMOJI_QG_FAIL = "⚠️"
EMOJI_DONE = ""
```
и подставить в f-строки. Текст сообщений НЕ менять — байт-в-байт тот же вывод. Это чисто читаемость.
**Тест:** не обязателен (поведение не меняется); если просто — добавь assert что константы непусты. Главное — существующие тесты plane_sync (если есть) зелёные.
**Коммит:** `refactor(plane_sync): extract emoji literals to constants (L-3)`
---
## Ограничения
- 🚫 НЕ трогай: nginx, openclaw.json, .env-секреты, deploy-хук, Plane-webhook is_active, ORCH-1/1b/2/4/5/6/7, stage_engine.py, STAGE_TRANSITIONS (логику), HMAC, очередь.
- Baseline: **145 passed**, 9 pre-existing 401 — НЕ чинить и НЕ ломать. Новые тесты зелёные, старые не падают.
- Conventional Commits, 3 отдельных коммита по пунктам.
- Тесты в контейнере: `IMG=$(docker inspect orchestrator --format '{{.Config.Image}}'); docker run --rm -v /home/slin/repos/orchestrator:/code -w /code --entrypoint python3 $IMG -m pytest tests/ -q`
## Проверка (Стрим проверит вживую)
| # | Что | Критерий |
|---|-----|----------|
| 1 | L-1 | stages.py:8 больше не врёт; логика/STAGE_TRANSITIONS не тронуты |
| 2 | L-2 | prune-функция + вызов; ошибка ротации не роняет app; активный лог не трогается; тест зелёный |
| 3 | L-3 | эмодзи в константах; вывод сообщений байт-в-байт прежний |
| 4 | тесты | мой прогон: было 145 → стало 145+новые passed, 9 pre-existing не тронуты |
| 5 | git | PR в main, remote содержит коммиты (грабля ORCH-7) |
## Отчёт
- НЕ деплоить, НЕ мержить (мерж делает Стрим после живой проверки).
- Запушь ветку (ПРОВЕРЬ remote!), открой PR в main. После каждого пункта — короткий отчёт.
- Если ТЗ расходится с кодом — отчитайся, не выдумывай. Один исполнитель, не паникуй про параллельные сессии.

View File

@@ -0,0 +1,98 @@
# DEV TASK — M-6: work_item_id из Plane sequence_id (источник правды = Plane)
**Проект:** orchestrator | **Сервер:** slin@82.22.50.71 | **Репо:** /home/slin/repos/orchestrator | **Контейнер:** orchestrator (8500)
**Ветка:** `feature/ORCH-M6-plane-sequence` из свежего main (git checkout main && git pull && git checkout -b feature/ORCH-M6-plane-sequence). main содержит ORCH-1/1b/2/4/5/6/7 + L1L2L3.
⚠️ **ГРАБЛЯ push (ORCH-7):** после push `git log origin/main..origin/feature/ORCH-M6-plane-sequence` ДОЛЖЕН показать твои коммиты ДО отчёта «PR готов».
## Проблема (верифицировано по живому коду + реальным payload в БД)
`db.get_next_work_item_id(repo, prefix)` нумерует `work_item_id` как `max(tasks по repo+prefix)+1`**независимо от Plane**. Plane ведёт свой `sequence_id`. При любом рассинхроне (ручное создание/удаление issue в Plane, гонка, пересоздание задачи) номер оркестратора (ET-003/ORCH-005) разъедется с тикетом в Plane → путаница.
**Цель:** источник правды для номера — **Plane `sequence_id`**, маппить детерминированно в `<prefix>-NNN`.
### Что уже есть (НЕ строй с нуля)
- `src/plane_sync.py` — рабочий httpx-клиент Plane: `PLANE_BASE`, `PLANE_HEADERS` (X-API-Key), `WORKSPACE` из `settings`. Уже читает `issue.get("sequence_id")` (стр.~99/115) и ходит на `{PLANE_BASE}/workspaces/{WORKSPACE}/projects/{project_id}/issues/...`.
- В `src/webhooks/plane.py:handle_work_item_created` в момент создания task УЖЕ доступны: `plane_id` (= issue UUID, стр.110), `plane_project_id` (стр.124), `proj.work_item_prefix`. Payload `work_item.created` содержит `id` и `project`, но **НЕ содержит `sequence_id`** (проверено на 5 реальных events) — поэтому нужен GET к Plane API по issue id.
## Что сделать
### 1. Helper: достать sequence_id из Plane по issue UUID (`src/plane_sync.py`)
Добавить функцию, напр.:
```python
def fetch_issue_sequence_id(issue_id: str, project_id: str) -> int | None:
"""GET the Plane issue by UUID and return its sequence_id (the authoritative
per-project number), or None if unavailable (network error / missing field)."""
url = f"{PLANE_BASE}/workspaces/{WORKSPACE}/projects/{project_id}/issues/{issue_id}/"
try:
resp = httpx.get(url, headers=PLANE_HEADERS, timeout=10)
resp.raise_for_status()
seq = resp.json().get("sequence_id")
return int(seq) if seq is not None else None
except Exception as e:
logger.warning(f"fetch_issue_sequence_id failed for {issue_id}: {e}")
return None
```
(подгони под фактический формат ответа Plane — проверь живым GET'ом, см. ниже).
### 2. Использовать sequence_id при создании task (`src/webhooks/plane.py:handle_work_item_created`)
Заменить безусловный `work_item_id = get_next_work_item_id(repo, proj.work_item_prefix)` на:
```python
seq = fetch_issue_sequence_id(plane_id, plane_project_id)
if seq is not None:
work_item_id = f"{proj.work_item_prefix}-{seq:03d}"
else:
# Plane недоступен / нет sequence_id → НЕ блокируем pipeline, падаем на DB-инкремент
work_item_id = get_next_work_item_id(repo, proj.work_item_prefix)
logger.warning(f"Plane sequence_id unavailable, fell back to DB increment: {work_item_id}")
```
⚠️ **Автономность важнее точности:** если Plane не ответил — НЕ ронять создание task, использовать старый fallback. Залогировать что был fallback.
### 3. Фикс хардкода `ET-` в `find_issue_id` (`src/plane_sync.py`, БОНУС, тот же скоуп)
Стр.~99: `identifier = f"ET-{seq:03d}"` — хардкодит префикс ET, сломано для ORCH-проектов из ORCH-6.
- Убрать хардкод `ET-`. Сравнивать по `sequence_id` напрямую с числом из `work_item_id` (распарсить `<prefix>-NNN` → NNN), а не собирать строку с фиксированным `ET-`.
- Аналогично fallback-блок стр.~103 (`if work_item_id.startswith("ET-")`) — обобщить на любой префикс (парсить число после последнего `-`).
- Логику поиска не ломать, только убрать ET-привязку.
## Ограничения
- 🚫 НЕ трогай: nginx, openclaw.json, .env-секреты, deploy-хук, Plane-webhook is_active, ORCH-1/1b/2/4/5/6/7, stage_engine.py, STAGE_TRANSITIONS, HMAC, очередь, `get_next_work_item_id` (оставить как fallback, НЕ удалять).
- Сетевой вызов к Plane — с timeout (есть, 10с) и try/except: ошибка НЕ должна ронять webhook-обработку.
- Baseline: **151 passed**, 9 pre-existing 401 — не чинить, не ломать.
- Conventional Commits: `feat(webhook): derive work_item_id from Plane sequence_id (M-6)`, `fix(plane_sync): drop hardcoded ET- prefix in find_issue_id (M-6)`.
## Разведка перед кодом (сделай ОБЯЗАТЕЛЬНО)
Проверь живым GET'ом РЕАЛЬНЫЙ формат ответа Plane по одному issue (есть ли `sequence_id` в одиночном GET, как называется поле):
```bash
docker exec orchestrator python3 -c "
import httpx
from src.config import settings
base=f'{settings.plane_api_url}/api/v1'; h={'X-API-Key':settings.plane_api_token}; ws=settings.plane_workspace_slug
# взять любой реальный issue_id из tasks
import sqlite3
r=sqlite3.connect('/app/data/orchestrator.db').execute(\"SELECT plane_issue_id, repo FROM tasks WHERE plane_issue_id IS NOT NULL ORDER BY id DESC LIMIT 1\").fetchone()
print('task:', r)
"
```
Если реальных issue в Plane нет (только fake-test-id) — проверь формат по списочному endpoint'у issues проекта. Если `sequence_id` приходит иначе (вложенный, другое имя) — адаптируй helper и **отчитайся что нашёл**, не выдумывай.
## Тесты
`tests/test_m6_sequence.py`:
- `fetch_issue_sequence_id` возвращает int при валидном ответе (мок httpx).
- возвращает None при сетевой ошибке / отсутствии поля (НЕ кидает).
- `handle_work_item_created`: при доступном seq → `work_item_id == f"{prefix}-{seq:03d}"`; при None → fallback на `get_next_work_item_id` (мок, assert вызван).
- `find_issue_id`: больше НЕ хардкодит `ET-` — работает для произвольного префикса (ORCH-005 матчится по sequence_id=5).
Тесты в контейнере: `IMG=$(docker inspect orchestrator --format '{{.Config.Image}}'); docker run --rm -v /home/slin/repos/orchestrator:/code -w /code --entrypoint python3 $IMG -m pytest tests/ -q`
## Проверка (Стрим проверит вживую)
| # | Что | Критерий |
|---|-----|----------|
| 1 | helper | GET по issue UUID → sequence_id; ошибка → None, не кидает |
| 2 | создание task | seq доступен → prefix-NNN из Plane; недоступен → fallback DB, залогирован |
| 3 | автономность | Plane down НЕ роняет webhook (task всё равно создаётся) |
| 4 | хардкод ET- | убран в find_issue_id, работает для ORCH-/любого префикса |
| 5 | тесты | мой прогон: 151 → 151+новые passed, 9 pre-existing не тронуты |
| 6 | git | PR в main, remote содержит коммиты (грабля ORCH-7) |
## Отчёт
- НЕ деплоить, НЕ мержить (мерж — Стрим после живой проверки). Запушь ветку (ПРОВЕРЬ remote!), открой PR в main.
- В отчёте обязательно: что реально вернул живой GET к Plane (формат sequence_id), какой fallback-путь, результаты прогона.
- После каждого блока — короткий отчёт. Если ТЗ расходится с кодом/Plane — отчитайся, не выдумывай. Один исполнитель, не паникуй про параллельные сессии.