# CLAUDE.md — паспорт проекта orchestrator ## TL;DR Мульти-агентный оркестратор разработки. FastAPI-сервис: принимает webhooks от Plane и Gitea, ведёт задачи по конвейеру стадий через Quality Gates, запускает Claude CLI агентов (analyst → architect → developer → reviewer → tester → deployer) на каждой стадии. **Оркестратор дорабатывает в том числе сам себя (self-hosting).** ## Стек - Backend: FastAPI + uvicorn (Python 3.12) - БД: SQLite (`src/db.py`) - Агенты: Claude CLI (`ORCH_CLAUDE_BIN`), по одному промпту на роль в `.openclaw/agents/`. **ORCH-74:** модель/эффорт агента берутся ТОЛЬКО из config (`resolve_agent_model`/`resolve_agent_effort`, ORCH-41) — frontmatter `model:` удалён как мёртвый, frontmatter описательный; имя модели валидируется форматом `^claude-…$` перед `--model` (never-break). - Очередь задач: собственная (SQLite `jobs`, `src/queue_worker.py`, ORCH-1). **ORCH-026:** `claim_next_job` гейтит задачи с незавершёнными зависимостями (`job_deps`, `NOT EXISTS`) без занятия слота `max_concurrency`; декларации/детект циклов — leaf `src/task_deps.py` (kill-switch `ORCH_TASK_DEPS_ENABLED`). Сериализация мержа одного репо — безусловный pre-merge rebase под merge-lease (`ORCH_PREMERGE_REBASE_ALWAYS`). **ORCH-088 (serial gate, Этап 1):** новая задача репо не входит в `analysis` (analyst-job не выбирается, ветка не режется), пока в репо есть **более ранняя** незавершённая задача (`t2.id < jobs.task_id`, FIFO) ИЛИ репо заморожен (`repo_freeze`). Срез ветки **отложен** со `start_pipeline` на момент claim analyst-job (`launcher._materialize_deferred_branch`) — база = свежий `origin/main` с кодом предшественника (анти-stale-base). Post-deploy `DEGRADED` → durable per-repo freeze (`repo_freeze`, `cleared_at IS NULL` = активен) + Telegram; снятие — вручную `POST /serial-gate/unfreeze?repo=…`. Leaf `src/serial_gate.py` (claim — fail-OPEN, freeze — fail-CLOSED); флаги `ORCH_SERIAL_GATE_ENABLED` (kill-switch), `ORCH_SERIAL_GATE_REPOS` (CSV; пусто = все репо), `ORCH_SERIAL_GATE_FREEZE_ENABLED`. Блок `serial_gate` в `GET /queue`. `STAGE_TRANSITIONS`/`QG_CHECKS` не тронуты. - Контейнеризация: Docker + Compose - CI/CD: Gitea Actions (`.gitea/workflows/`) - Деплой: docker compose на mva154 ## Команды - `uvicorn src.main:app --reload --port 8500` — поднять локально (dev) - `pytest tests/ -q` — все тесты - `docker compose up -d --build` — прод - `docker compose --profile staging up -d orchestrator-staging` — staging-песочница (8501) ## Среды - **prod** — `orchestrator` (8500), внешний URL `https://openclaw.mva154.duckdns.org/orchestrator/` - **staging** — `orchestrator-staging` (8501), изолированная БД (`./data/staging`), только sandbox-проект ## Структура - `src/` — приложение (main, config, db, stages, stage_engine, queue_worker, projects, usage) - `src/agents/launcher.py` — запуск Claude CLI агентов - `src/qg/checks.py` — Quality Gate проверки - `src/webhooks/` — приём вебхуков Plane/Gitea - `tests/` — pytest - `docs/` — документация, ADR, work-items, operations - `scripts/` — утилиты (staging_check.py, orchestrator-deploy-hook.sh) ## Конвейер (кратко; детали — docs/architecture/README.md) ``` created → analysis → architecture → development → review → testing → deploy-staging → deploy → done ↑ │ └──── REQUEST_CHANGES ──────┘ (откат на development, max 3) ``` ## Статусная модель Plane (ORCH-066) — индикация ≠ управление Статусы Plane — это **слой B (индикация)**, отдельный от **слоя A (машина стадий)** `src/stages.py::STAGE_TRANSITIONS`. Plane показывает наблюдателю осмысленную картину (`Backlog → Todo → Analysis → Architecture → Development → Code-Review → Testing → Awaiting Deploy → Deploying → Monitoring after Deploy → Done` + человеческие гейты `In Review/Approved`, `Confirm Deploy`), но НИКОГДА не управляет конвейером. Маппинг и сеттеры — `src/plane_sync.py` (6 новых ключей: `to_analyse/analysis/code_review/awaiting_deploy/deploying/monitoring`), с project-relative alias-fallback: на частично сконфигурированном проекте новый ключ деградирует на базовый UUID ТОГО ЖЕ проекта (нулевая регрессия для enduro-trails). Детали — `docs/architecture/README.md`. ## Нотификации / Telegram live-tracker (ORCH-042/066/067/087) Каждая задача = **одна карточка** в Telegram (`src/notifications.py`). Поведение карточки: - **Дефолт `tracker_mode` — `bump`** (ORCH-067; `edit` доступен через `ORCH_TRACKER_MODE=edit`). `bump` на каждом обновлении удаляет старую карточку и шлёт свежую вниз чата (тихо), `edit` редактирует на месте. Инвариант «одна карточка на задачу» — в обоих режимах. - **Зачистка сирот (ORCH-087):** bump ведёт авторитетный леджер ВСЕХ созданных карточек (таблица `tracker_messages`, `deleted_at IS NULL` = жива) и на каждом обновлении удаляет ВСЕ незакрытые mid, а не только скаляр `tracker_message_id` (он сохранён как указатель на текущую карточку, BC). Это устраняет класс «замёрзшая сирота» (старая карточка с заголовком ранней стадии, потерявшая ссылку при гонке/`delete`-fail+`send`-ok). Новый mid пишется в леджер ТОЛЬКО при успешном `send` (BR-6); transient-`delete` остаётся незакрытым для ретрая; «already gone»/>48ч (`_DELETE_GONE_MARKERS`) → закрывается. Остаточная гонка самозалечивается за один bump. Known-limitation: Telegram 48ч (сироты старше неудаляемы). - **Эффорт в строке стадии (ORCH-087):** колонка `agent_runs.effort` стампится фактическим `resolve_agent_effort` в `launcher._spawn` (CLI его в result-JSON не возвращает); строка рендерится `· {model} · {effort}` (developer=`xhigh`, tester/deployer=`medium`, прочие=`high`); пустой/исторический effort → суффикс опускается. - **Честное итоговое время (ORCH-087):** done-строка = три независимых подписанных метрики `⏱️ Агенты {Σ agent_runs} · твоё {review~cap} · общее с ожиданием {wall}` (раньше `Всего {wall}` читалось как сумма, которой не является). «Твоё» ограничено `tracker_brd_review_cap_s` (`ORCH_TRACKER_BRD_REVIEW_CAP_S`, дефолт 2ч; маркер `~` при отсечке аномального застоя). - **Статус-строка карточки** (`📍 `) показывает текущий Plane-статус по модели ORCH-066 (`plane_status_label`). Оффлайн-ядро (`stage → статус`, In Review из brd-clock) работает всегда без сети; best-effort live-overlay (kill-switch `tracker_live_status`, TTL-кэш, короткий таймаут) лишь дорисовывает ветки, неотличимые offline (Needs Input / Blocked / Rejected / Cancelled / **Confirm Deploy** / Deploying / Monitoring) и **никогда не блокирует конвейер**. - **Кликабельный номер задачи** (`plane_issue_link`) — `ORCH-NNN` в карточке И во всех уведомлениях (`notify_*`, alert'ы стадий) рендерится как `` на issue в Plane; fail-safe → просто `html.escape(номер)`, если ссылку построить нельзя. Никогда не падает. - **Без link-preview (ORCH-080):** оба примитива (`send_telegram`/`edit_telegram`) шлют payload с `disable_web_page_preview: True` — баннер Plane («Modern project management») под кликабельной ссылкой `ORCH-NNN` больше не разворачивается ни в карточке (`bump`/`edit`), ни в notify/alert-сообщениях. `parse_mode: HTML` сохранён → ссылка остаётся кликабельной. - Транспорт (`send_telegram`/`edit_telegram`/`delete_telegram`), `disable_notification` (карточка тихая, пингуют только alert-хелперы), схема БД — не трогаются. ## Авто-режим по лейблам: autoApprove + autoDeploy (ORCH-089) Конвейер имеет два **человеческих** гейта, тормозящих пакетный автономный прогон (эпик ORCH-088): гейт BRD (`analysis`: ручной `Approved`) и гейт прод-деплоя (`deploy` Phase A: ручной `Confirm Deploy`, ORCH-059). ORCH-089 снимает **только эти два человеческих решения** — выборочно (лейбл Plane на задаче), декларативно, обратимо, **не трогая ни одной технической проверки**. Инвариант: авто-режим снимает лишь ожидание человеческого сигнала; `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/схема БД — **не трогаются**. Аддитивно: leaf `src/labels.py` (never-raise) + две точечные врезки. - **`autoApprove`** → врезка в `stage_engine._handle_analysis_approved_flow` (ветка `files_ok`): `set_issue_approved` (индикация) + лог/Telegram/Plane-коммент + `advance_stage(..., finished_agent=None)` — **тот же путь, что человеческий Approved** (`approved-via-status` → `analysis → architecture` + `mark_brd_review_ended`). - **`autoDeploy`** → врезка в `stage_engine._handle_self_deploy_phase_a` после advance на `deploy` + `clear_state`: лог/Telegram/Plane-коммент + `_handle_self_deploy_phase_b` (маркер `INITIATED`, статус `Deploying`, finalizer). Пропускаются лишь индикативно-человеческие шаги. **BR-5 структурно:** Phase A достигается только после зелёных под-гейтов ребра `deploy-staging → deploy` (security → merge-gate → image-freshness → staging) → autoDeploy физически не деплоит сломанное. - **Чтение лейблов** — `plane_sync.fetch_issue_labels` (`None` при ошибке ≠ `[]`) + `get_project_labels` (`{normalized_name→uuid}`, TTL-кэш); сопоставление по нормализованному имени (`strip().casefold()`), неоднозначность → «нет лейбла». Источник истины — Plane API, не payload вебхука. Новый сеттер `set_issue_approved`. - **Флаги** (`config.py`): `auto_label_enabled` (kill-switch), `auto_approve_label`/ `auto_deploy_label`, `auto_label_repos` (CSV; **пусто → self-hosting only**), `auto_label_states_ttl_s`. `applies(repo)` (локальный) проверяется ПЕРВЫМ; `has_label` (сеть) — только при `applies==True` → при выключенном флаге нулевой сетевой оверхед. - **Fail-safe (never auto):** любая ошибка/недоступность Plane/неоднозначность → «нет авто» → ручной гейт (never-raise). Прозрачность: лог + Telegram + Plane-коммент + live-карточка; блок `auto_labels` в `GET /queue`. **Инфра-предусловие:** создать лейблы `autoApprove`/`autoDeploy` в Plane-проекте ORCH (их отсутствие = ручной режим, fail-safe). Детали — `docs/work-items/ORCH-089/06-adr/ADR-001-auto-label-gates.md`, `docs/architecture/adr/adr-0018-auto-label-gates.md`. ## Конвенции - Conventional Commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`) - Ветки: `feature/ORCH-NNN-slug`, `fix/ORCH-NNN-slug` - ADR per work-item: `docs/work-items//06-adr/ADR-NNN-slug.md` - Global ADR (сквозные решения): `docs/architecture/adr/adr-NNNN-slug.md` - Work items: `docs/work-items//` - Машинные вердикты Quality Gate — строго YAML-frontmatter (`verdict:`, `deploy_status:`, `staging_status:`, `security_status:`), никогда проза. **ORCH-52c (ORCH-076):** парсинг frontmatter сведён к единому контракту `src/frontmatter.py` (reader `read_frontmatter_value` — BC; единый парс-примитив `parse_frontmatter`; writer `render/write_frontmatter`; валидатор схемы `validate_schema`/`REQUIRED_FIELDS` — warning-only по умолчанию, hard-fail только под kill-switch `frontmatter_validation_strict`, дефолт `False`). Пять вердикт-парсеров (`check_reviewer_verdict`, `_parse_tests_verdict`, `_parse_deploy_status`, `_parse_staging_status`, `parse_security_status`) читают через ОДНУ точку парсинга; семантика вердиктов и `STAGE_TRANSITIONS`/состав `QG_CHECKS` — 1:1. Формальная спека «стадия → обязательный выход» + обязательная frontmatter-схема — `docs/_standards/HANDOFF_PROTOCOL.md` ## Артефакты задачи (`docs/work-items//`) `00-business-request.md`, `01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`, `04-test-plan.yaml`, `06-adr/ADR-NNN-slug.md`, `07-infra-requirements.md`, `08-data-requirements.md`, `10-tech-risks.md`, `12-review.md`, `13-test-report.md`, `14-deploy-log.md`, `15-staging-log.md`, `16-post-deploy-log.md` (post-deploy наблюдение, ORCH-021), `17-security-report.md` (security-гейт: `security_status:`/secrets/deps, ORCH-022). **Стандарт документов (ORCH-075, ORCH-52b):** структура каждого дока, карта «стадия→агент→документ→гейт→machine-key» и конвенция ADR-naming зафиксированы в `docs/_standards/PIPELINE_DOCS.md` (golden source); копируемые скелеты — в `docs/_templates/`. Перед написанием номерного дока бери скелет из `docs/_templates/` и не меняй имя machine-key frontmatter (регистр чувствителен — иначе гейт упадёт ложно). ## Правила для агентов 1. Перед любым действием прочесть этот файл и `docs/architecture/README.md`. 2. **Документация = golden source наравне с кодом.** Изменил функционал → обнови доку В ТОМ ЖЕ PR. Архитектурное решение → заведи ADR (формат — `docs/_standards/PIPELINE_DOCS.md` §4). Структура номерных доков и шаблоны — `docs/_standards/PIPELINE_DOCS.md` + `docs/_templates/`. Обнови `CHANGELOG.md`. 3. Никогда не править артефакты других этапов. 4. Никогда не комментировать ТЗ задним числом — если ТЗ не годится, возвращай в Анализ. 5. Никогда не закрывать задачу самостоятельно — это делает CI / финальная стадия. 6. **Reviewer проверяет: обновлена ли документация. Нет → REQUEST_CHANGES.** 7. Не использовать `--no-verify` без явного одобрения Owner. 8. Секреты — только в `.env`/`.env.staging` на хосте, в гит НЕ коммитятся (канон — `.env.example`). ## ⚠️ Self-hosting — оркестратор правит САМ СЕБЯ Задачи проекта ORCH меняют инструмент, который СЕЙЧАС работает в продакшене и обслуживает ДРУГИЕ проекты (enduro-trails) из ОДНОГО инстанса с ОБЩЕЙ БД и общей очередью. - **НЕ перезапускать / не ронять прод-контейнер** `orchestrator` в рамках задачи — встанет конвейер всех проектов. - Любой деплой/рестарт self = групповой риск. Детали и топология — `docs/operations/INFRA.md`. - Стадия `deploy-staging` (порт 8501) — обязательная страховка перед прод-деплоем орка. - Прод-деплой орка запускается ТОЛЬКО переводом задачи на стадии `deploy` в выделенный Plane-статус **«Confirm Deploy»** (ORCH-059). Статус `Approved` — человеческий гейт конвейера и прод-деплой НЕ запускает (на `deploy` — no-op). Это разделяет «одобрить артефакт» и «выкатить в прод», чтобы привычный approve не ронял прод случайным кликом. --- *Паспорт проекта orchestrator. Поддерживается агентами при каждой доработке. Изолирован: описывает только этот проект (канон per-repo, см. ORCH-9).*