From d97b26a59f12424c4694f11b2d223609ed12ee75 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Tue, 9 Jun 2026 16:23:17 +0300 Subject: [PATCH] =?UTF-8?q?docs(ORCH-079):=20ORCH-52f=20=E2=80=94=20sync?= =?UTF-8?q?=20README=20with=20code=20+=20reviewer=20overview-docs=20axis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layer 5 (final) of epic ORCH-52. Docs + prompt-only; src/ untouched. - README.md «Известные ограничения»: fix numbering (was 1,2,3,4,3,4), move 6 resolved/obsolete items to «Закрыто (история)» trail with ORCH refs, keep only really-open limitations (Telegram-48h ORCH-087, task-deps intra-repo ORCH-026, serial-gate ORCH-088). Point-sync stage table (development → check_ci_green) and event-routing (ORCH-045). - reviewer.md: overview-docs axis (axis 4 + constraints) — closing a README limitation without updating README → finding ≥P1 (canon 52d «❌→✅»; verdict key + 5 XML sections + 6 schema fields byte-intact). - tests: new tests/test_readme_limitations.py (numbering + no resolved items as open); test_agent_prompts_canon.py asserts the new axis. - CLAUDE.md / CHANGELOG.md updated; epic ORCH-52 closed (52b→…→52f). Refs: ORCH-079 Co-Authored-By: Claude Opus 4.8 --- .openclaw/agents/reviewer.md | 8 +++ CHANGELOG.md | 5 ++ CLAUDE.md | 2 +- README.md | 26 ++++++--- tests/test_agent_prompts_canon.py | 21 ++++++++ tests/test_readme_limitations.py | 88 +++++++++++++++++++++++++++++++ 6 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 tests/test_readme_limitations.py diff --git a/.openclaw/agents/reviewer.md b/.openclaw/agents/reviewer.md index 4331c3a..0d4865f 100644 --- a/.openclaw/agents/reviewer.md +++ b/.openclaw/agents/reviewer.md @@ -48,6 +48,11 @@ tools: `docs/architecture/README.md` и/или `docs/architecture/internals.md`? конфигурация → `README.md` (таблица env)? новый компонент → `docs/architecture/README.md`? обновлён `CHANGELOG.md`? архитектурное решение → есть ADR? + - **Обзорные доки (ORCH-079):** если PR закрывает/меняет пункт из `README.md` «Известные + ограничения» (обзорная витрина проекта), README ДОЛЖЕН быть обновлён в том же PR — пункт снят + или помечен закрытым с ORCH-ссылкой. Необновление обзорных доков → **finding ≥ P1**; если + ограничение закрыто правкой `src/` без обновления README — это совпадает с P0 «`src/` изменён, + документация не обновлена». Это усиление трактовки оси, а не отдельная ось. @@ -65,6 +70,9 @@ frontmatter-вердиктом, см. ``). - ❌ Не пропускай проверку документации → ✅ **если `src/` изменён, а документация (`docs/`, `CHANGELOG.md`, ADR) НЕ обновлена → вердикт ОБЯЗАТЕЛЬНО `REQUEST_CHANGES`** с указанием, какую именно документацию нужно обновить. Документация = golden source наравне с кодом. +- ❌ PR закрыл пункт из `README.md` «Известные ограничения», но README не обновлён (пункт остался + открытым) → ✅ требуй обновления обзорных доков — пункт снят либо помечен закрытым с ORCH-ссылкой; + необновление обзорной витрины → **finding ≥ P1** (ORCH-079). **Severity:** - **P0 (blocker):** не реализовано требование ТЗ; нарушен ADR; критическая уязвимость; diff --git a/CHANGELOG.md b/CHANGELOG.md index 7afafbf..3328e7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ Формат: [Keep a Changelog](https://keepachangelog.com/). Записи — на смысловой PR/задачу. ## [Unreleased] +- **Синхронизация обзорных доков (README) с кодом + reviewer-ось «обзорные доки»** (ORCH-079 / ORCH-52f, `docs`): слой 5 (финал) эпика ORCH-52, замыкающий цепочку 52b (структура) / 52c (frontmatter) / 52d (промпты) / 52e (трассировка). Корневой `README.md` — обзорная витрина проекта — **выдавал решённое за открытое**: секция «Известные ограничения» имела битую нумерацию (`1,2,3,4,3,4`) и пункты, опровергнутые кодом. **Docs + prompt-only:** `src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`/`_parse_*`, `src/frontmatter.py`, схема БД — **не тронуты**; `frontmatter_validation_strict` остаётся `False`; новый QG не вводится; правило обзорных доков нормативно-описательное (не машинный гейт), как ось трассировки ORCH-078. + - **`README.md` приведён в честное состояние по коду (FR-1/FR-2/FR-3, AC-1/AC-2/AC-3):** перенумерация «Известные ограничения» сквозная без повторов; 6 решённых/устаревших пунктов перенесены в трейл **«Закрыто (история)»** с ORCH-ссылками (worktree → `ensure_worktree`+ORCH-026/088; in-process daemon → очередь ORCH-1; «Gitea CI не настроен» → `check_ci_green`; «no retry» → backoff/breaker `queue_worker.py`+ORCH-045; issue-ID → зрелый `plane_sync` ORCH-010/066/068; Playwright-timeout → watchdog ORCH-7); в «открытых» — только реально открытые, верифицированные кодом/задачей (Telegram-48h ORCH-087, task-deps intra-repo v1 ORCH-026, serial-gate Этап 1 ORCH-088). Точечная сверка с кодом: стадия `development` в таблице — `check_ci_green` (был устаревший `check_tests_local`); строка event-routing `status` — авторитетный гейт развития `check_ci_green` (ORCH-045), убран legacy-текст «больше не authoritative». + - **Reviewer-ось «обзорные доки» (FR-5, AC-5):** `.openclaw/agents/reviewer.md` ось 4 «Документация» (``) + `` несут точечную врезку «❌→✅» (канон 52d): *PR закрыл пункт README «Известные ограничения», README не обновлён → finding ≥P1*; при закрытии правкой `src/` без обновления README — совпадает с существующим P0. Машинный ключ `verdict: APPROVED|REQUEST_CHANGES` — байт-в-байт; 5 XML-секций и 6 полей схемы 52c сохранены. Правило в одном промпте (без выноса в `docs/_standards/`, в отличие от 52e). + - **Эпик ORCH-52 закрыт:** 52b (adr-0019) → 52c (adr-0020) → 52d (adr-0021) → 52e (adr-0022) → **52f (adr-0023)**. Сквозной `docs/architecture/adr/adr-0023-overview-docs-reviewer-axis-and-epic52-close.md` + per-work-item `docs/work-items/ORCH-079/06-adr/ADR-001-readme-sync-and-reviewer-overview-docs-axis.md`. + - **Анти-регресс (FR-6, AC-6):** новый структурный `tests/test_readme_limitations.py` (нумерация без повторов; решённые пункты не значатся открытыми; трейл «Закрыто» с ORCH-ссылками); расширен `tests/test_agent_prompts_canon.py` (assert наличия оси обзорных доков в `reviewer.md`); канон 52d (5 секций, 6 полей, регистр verdict-ключей) и `test_agent_frontmatter_no_model.py` зелёные; полный регресс `tests/` зелёный (1257). Документация: `README.md`, `docs/architecture/README.md` (слой 5 эпика 52), `CLAUDE.md`. Полностью обратимо `git revert` (нет машинного поведения/состояния/kill-switch). - **Стандарт маркеров-трассировки `ORCH-NNN` + правило чтения ADR перед правкой** (ORCH-078 / ORCH-52e, `docs`): слой 4 (трассировка) эпика ORCH-52, замыкающий цепочку 52b (структура) / 52c (frontmatter) / 52d (промпты). Маркеры `ORCH-NNN`/`ET-NNN` в коде (де-факто 51 уникальный в `src/`) привязывают нетривиальные инварианты к породившему их work item — была сложившаяся практика без формального контракта. **Docs + prompts-only:** `src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`/`_parse_*`, `src/frontmatter.py`, схема БД — **не тронуты**; `frontmatter_validation_strict` остаётся `False`; новый QG не вводится; массовый ретро-фит 51 маркера вне объёма (стандарт нормативен «на будущее»). - **Новый стандарт `docs/_standards/TRACEABILITY.md`** (рядом с `PIPELINE_DOCS.md`/`HANDOFF_PROTOCOL.md`): формат маркера, правило размещения (рядом с нетривиальным инвариантом), чтение истории с реальным проверяемым примером (`src/serial_gate.py` → ORCH-088 → `ADR-001-serial-gate.md`), fallback-доступ (`git show origin/main:docs/work-items/...`), анти-археология (3+ маркеров → сводный сквозной ADR), каноничный текст правила чтения (единый источник). - **Точечные врезки в промпты (аддитивно, 52d-канон не переписан):** `developer.md` — правило чтения чужого маркера + fallback («❌ X → ✅ Y»); `architect.md` — правило чтения + анти-археология (3+ → сквозной ADR); `reviewer.md` — усиление оси «Соответствие ADR» под-пунктом «правка маркированного кода сверена с ADR; слом → finding ≥P1». Все три **ссылаются** на единый текст в `TRACEABILITY.md`, не копируют (анти-дубль BR-6). diff --git a/CLAUDE.md b/CLAUDE.md index abf64b3..855e012 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -130,7 +130,7 @@ created → analysis → architecture → development → review → testing → 3. Никогда не править артефакты других этапов. 4. Никогда не комментировать ТЗ задним числом — если ТЗ не годится, возвращай в Анализ. 5. Никогда не закрывать задачу самостоятельно — это делает CI / финальная стадия. -6. **Reviewer проверяет: обновлена ли документация. Нет → REQUEST_CHANGES.** +6. **Reviewer проверяет: обновлена ли документация. Нет → REQUEST_CHANGES.** Это включает **обзорные доки** (ORCH-079, 52f — финал эпика 52): если PR закрывает пункт `README.md` «Известные ограничения», но README не обновлён → finding ≥P1 (витрина проекта не должна выдавать решённое за открытое). 7. Не использовать `--no-verify` без явного одобрения Owner. 8. Секреты — только в `.env`/`.env.staging` на хосте, в гит НЕ коммитятся (канон — `.env.example`). 9. **Трассировка маркеров (ORCH-078, ORCH-52e):** правишь строку/блок с маркером `ORCH-NNN` → diff --git a/README.md b/README.md index 8a1adce..0b116c4 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ created → analysis → architecture → development → review → testing → | created | — | — | Plane webhook (work_item.created) | | analysis | analyst | Файлы BRD/TRZ/AC/TestPlan | Push docs/ | | architecture | architect | ADR или infra-requirements | Push docs/ | -| development | developer | check_tests_local (орк сам гоняет `make test`) | Auto-advance после developer | +| development | developer | check_ci_green (Gitea CI зелёный на ветке) | Auto-advance после developer | | review | reviewer | check_reviewer_verdict (`verdict:` во frontmatter 12-review.md) | Auto-advance после reviewer | | testing | tester | check_tests_passed (test-report.md) | Auto-advance после tester | | deploy-staging | deployer | check_staging_status (15-staging-log.md) | Auto-advance после tester | @@ -223,7 +223,7 @@ stdout/stderr агента перенаправляются СРАЗУ в `/app/ Gitea events роутятся по типу: - `push` → проверка файлов, advance architecture/development - `pull_request*` (wildcard) → review approved/rejected, PR merge -- `status` → (legacy) Gitea CI; С-1: больше не authoritative, `failure` логируется на debug и не блокирует/не алертит (QG развития = локальный `check_tests_local`) +- `status` → Gitea CI статус; ORCH-045: авторитетный гейт развития (`development → review`) — `check_ci_green` читает статус ветки с polling-retry (устраняет гонку «pending сразу после push») ## Тесты @@ -233,9 +233,19 @@ pytest tests/ -v ## Известные ограничения -1. **Single-task / shared `/repos` checkout** — одновременно безопасно обрабатывается одна задача: все агенты и `check_tests_local` делают `git checkout` в одном `/repos/` → гонки при параллельных задачах. Исправление — git worktree per task (S-4, отдельно). -2. **Plane sync** — маппинг issue ID может быть некорректным (P3, в работе) -3. **In-process daemon-потоки** — агенты живут в потоках uvicorn; при рестарте ловит orphan-recovery. Целевое — очередь задач (F-2b) -4. **Gitea CI не настроен** — тесты гоняет сам оркестратор локально -3. **Tester timeout** — e2e тесты с Playwright могут занимать >25 мин на тяжёлых фичах -4. **No retry on API errors** — httpx вызовы к Gitea/Plane без retry logic +Реально открытые ограничения (сверено с кодом, ORCH-079): + +1. **Telegram 48h** — карточки-сироты старше 48 часов неудаляемы (лимит Telegram Bot API); зачистка сирот самозалечивает только свежие (ORCH-087). +2. **Зависимости задач — только intra-repo (v1)** — `job_deps` выражают связи в пределах одного репозитория; кросс-репо зависимости пока не поддержаны (ORCH-026). +3. **Пакетный автоном — Этап 1** — per-repo serial gate сериализует задачи одного репо (ORCH-088); полный пакетный автономный прогон «10–20 задач за ночь» — в развитии (эпик ORCH-088). + +### Закрыто (история) + +Пункты, ранее значившиеся ограничениями, закрыты кодом — оставлены как трассировка: + +- **Single-task / shared `/repos` checkout** → git worktree per task (`ensure_worktree`) + serial-gate (ORCH-088) + task-deps (ORCH-026). +- **In-process daemon-потоки** → персистентная очередь задач (SQLite `jobs`, `src/queue_worker.py`), restart-safe (ORCH-1). +- **Gitea CI не настроен** → активный гейт стадии `development` — `check_ci_green` (`src/qg/checks.py`); `check_tests_local` помечен DEPRECATED. +- **No retry on API errors** → exp-backoff + circuit breaker в `queue_worker.py` (`ORCH_BACKOFF_*` / `ORCH_BREAKER_*` / `ORCH_TRANSIENT_MAX_ATTEMPTS`) + retry-loop в `check_ci_green` (ORCH-1 resilience / ORCH-045). +- **Plane sync — маппинг issue ID** → зрелый `src/plane_sync.py` (`find_issue_id`, `fetch_issue_sequence_id`) со статус-моделью и TTL-самозалечиванием (ORCH-010 / 066 / 068). +- **Tester timeout — Playwright e2e** → orchestrator является pytest-сервисом (Playwright неприменим); реальный механизм — конфигурируемый watchdog (`agent_timeout_seconds`, ORCH-7). diff --git a/tests/test_agent_prompts_canon.py b/tests/test_agent_prompts_canon.py index 2051c36..f0491ee 100644 --- a/tests/test_agent_prompts_canon.py +++ b/tests/test_agent_prompts_canon.py @@ -258,3 +258,24 @@ def test_claude_md_and_readme_reference_traceability_standard(): assert "TRACEABILITY.md" in _read_repo("docs", "architecture", "README.md"), ( "architecture README does not reference docs/_standards/TRACEABILITY.md" ) + + +# --------------------------------------------------------------------------- # +# ORCH-079 (ORCH-52f): reviewer overview-docs axis (layer 5 of epic ORCH-52). +# Pure-text anti-drift check (TRZ §FR-6 / AC-5), NO `src/` import. +# --------------------------------------------------------------------------- # + +def test_reviewer_carries_overview_docs_axis(): + """ORCH-079 TC-01 (AC-5): reviewer.md covers the README overview-docs axis. + + The reviewer must require README ("Известные ограничения") to be updated when + a PR closes a documented limitation. This guards the rule against silent drift + in a future prompt refactor, exactly like the traceability control axis. + """ + text = _read("reviewer") + assert "Известные ограничения" in text, ( + "reviewer.md does not mention the README 'Известные ограничения' overview-docs axis" + ) + assert "ORCH-079" in text, ( + "reviewer.md does not anchor the overview-docs axis to ORCH-079" + ) diff --git a/tests/test_readme_limitations.py b/tests/test_readme_limitations.py new file mode 100644 index 0000000..7049c7f --- /dev/null +++ b/tests/test_readme_limitations.py @@ -0,0 +1,88 @@ +"""ORCH-079 (ORCH-52f): structural anti-drift for README "Известные ограничения". + +Layer 5 (final) of epic ORCH-52: the root README overview showcase must not lie +about the project state. These are pure-text structural checks (NO `src/` import, +NO agent runs) guarding two invariants (TRZ §FR-1/FR-2, AC-1/AC-2): + + * the OPEN limitations list is numbered strictly 1, 2, 3, … without repeats + (the historical bug was `1,2,3,4,3,4`); + * resolved/obsolete items (single-task worktree, in-process daemon, "Gitea CI + not configured", "no retry") are NOT listed as OPEN limitations — if mentioned + at all, only under the "Закрыто (история)" trail. + +Covers test-plan TC-05. +""" +import os +import re + +_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_README = os.path.join(_REPO_ROOT, "README.md") + +# Heading of the limitations section and the closed-history subsection that ends +# the OPEN portion. +_SECTION_HEAD = "## Известные ограничения" +_CLOSED_HEAD = "Закрыто (история)" + +# Phrases that mark a RESOLVED/obsolete item. They must not appear in the OPEN +# portion of the section (only allowed under "Закрыто (история)"). +_RESOLVED_MARKERS = ( + "Single-task", + "shared `/repos`", + "daemon-потоки", + "не настроен", # "Gitea CI не настроен" + "No retry", +) + + +def _read_readme() -> str: + with open(_README, encoding="utf-8") as f: + return f.read() + + +def _limitations_section() -> str: + """Text of the '## Известные ограничения' section up to the next '## ' heading.""" + text = _read_readme() + idx = text.find(_SECTION_HEAD) + assert idx != -1, "README.md has no '## Известные ограничения' section" + rest = text[idx + len(_SECTION_HEAD):] + # Stop at the next top-level (##) heading, if any. + nxt = re.search(r"\n## ", rest) + return rest[: nxt.start()] if nxt else rest + + +def _open_portion(section: str) -> str: + """The OPEN-limitations portion: everything before the 'Закрыто (история)' trail.""" + cut = section.find(_CLOSED_HEAD) + return section[:cut] if cut != -1 else section + + +def test_open_limitations_numbered_sequentially(): + """AC-1: the OPEN limitations list is numbered 1, 2, 3, … with no repeats/gaps.""" + open_part = _open_portion(_limitations_section()) + # Leading numbered list items: lines like "1. **...". + numbers = [int(m) for m in re.findall(r"^(\d+)\.\s", open_part, flags=re.MULTILINE)] + assert numbers, "no numbered OPEN limitations found in README section" + assert numbers == list(range(1, len(numbers) + 1)), ( + f"OPEN limitations numbering is not strictly sequential: {numbers}" + ) + + +def test_resolved_items_not_listed_as_open(): + """AC-2: resolved/obsolete items are not present as OPEN limitations.""" + open_part = _open_portion(_limitations_section()) + leaked = [m for m in _RESOLVED_MARKERS if m in open_part] + assert not leaked, ( + f"resolved items leaked into OPEN limitations (must be under 'Закрыто'): {leaked}" + ) + + +def test_closed_history_trail_present_with_orch_refs(): + """AC-2: the 'Закрыто (история)' trail exists and carries ORCH references.""" + section = _limitations_section() + assert _CLOSED_HEAD in section, ( + "README limitations section lacks the 'Закрыто (история)' trail" + ) + closed = section[section.find(_CLOSED_HEAD):] + assert re.search(r"ORCH-\d+", closed), ( + "the 'Закрыто (история)' trail has no ORCH-NNN references" + )