docs(ORCH-079): ORCH-52f — sync README with code + reviewer overview-docs axis
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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/` изменён,
|
||||
документация не обновлена». Это усиление трактовки оси, а не отдельная ось.
|
||||
</task>
|
||||
|
||||
<deliverables>
|
||||
@@ -65,6 +70,9 @@ frontmatter-вердиктом, см. `<output_format>`).
|
||||
- ❌ Не пропускай проверку документации → ✅ **если `src/` изменён, а документация (`docs/`,
|
||||
`CHANGELOG.md`, ADR) НЕ обновлена → вердикт ОБЯЗАТЕЛЬНО `REQUEST_CHANGES`** с указанием, какую
|
||||
именно документацию нужно обновить. Документация = golden source наравне с кодом.
|
||||
- ❌ PR закрыл пункт из `README.md` «Известные ограничения», но README не обновлён (пункт остался
|
||||
открытым) → ✅ требуй обновления обзорных доков — пункт снят либо помечен закрытым с ORCH-ссылкой;
|
||||
необновление обзорной витрины → **finding ≥ P1** (ORCH-079).
|
||||
|
||||
**Severity:**
|
||||
- **P0 (blocker):** не реализовано требование ТЗ; нарушен ADR; критическая уязвимость;
|
||||
|
||||
@@ -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 «Документация» (`<task>`) + `<constraints>` несут точечную врезку «❌→✅» (канон 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).
|
||||
|
||||
@@ -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` →
|
||||
|
||||
26
README.md
26
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/<repo>` → гонки при параллельных задачах. Исправление — 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).
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
88
tests/test_readme_limitations.py
Normal file
88
tests/test_readme_limitations.py
Normal file
@@ -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"
|
||||
)
|
||||
Reference in New Issue
Block a user