---
pr: 67
title: feat(pipeline): add check_project_map to DOCS phase
---

## Что сделано

- **`.opencode/scripts/pipeline-status.py`** — 3 точечные правки:
  - Константа `PROJECT_MAP_DIR = REPO_ROOT / "docs" / "project-map"` (после
    `ADR_DIR`, строка 54).
  - Функция `check_project_map()` (после `check_adr`) — проверяет
    `PROJECT_MAP_DIR.exists()` + `README.md.exists()`, возвращает
    DONE/NOT_DONE (без AMBIGUOUS, чисто файловая система). Шаблон по образцу
    `check_adr`.
  - В `check_docs` вставлен вызов между `check_adr` и
    `_check_docs_reviewer_comment` (short-circuit на NOT_DONE). Docstring
    `check_docs` обновлён (теперь 5 детерминированных проверок).
- **`tests/test_pipeline_status_project_map.py`** — новый файл, 3 unit-теста
  (по образцу `test_pipeline_status_adr.py`):
  - `test_project_map_exists` — README.md есть → DONE, "README.md" в detail.
  - `test_project_map_dir_missing` — директория отсутствует → NOT_DONE,
    "директория" в detail.
  - `test_project_map_readme_missing` — директория есть, README.md нет →
    NOT_DONE, "README.md не найден" в detail.
  Загрузка скрипта под уникальным module name `pipeline_status_project_map`
  (не коллидирует с `pipeline_status` из других тестов).
- **`tests/test_pipeline_status.py`** — критическая правка существующих
  тестов:
  - Добавлен helper `make_project_map(tmp_path)` рядом с `make_handoff` —
    создаёт `tmp_path/project-map/README.md`.
  - Во все 10 `test_check_docs_*` тестов добавлен
    `monkeypatch.setattr(ps, "PROJECT_MAP_DIR", make_project_map(tmp_path))`.
    Без этого новая под-проверка возвращала NOT_DONE и ломала assertions
    (7 тестов с валидным ADR доходили до check_project_map).
- **ADR-028** (`docs/decisions/028-pr-67-project-map-contract.md`) —
  фиксирует контракт project-map: README.md обязателен (mandatory, enforced),
  модульные файлы и frontmatter `last_updated` — aspirational (не enforced).
  Альтернативы: полный контракт (отклонено — блокирует пайплайн, ~12 файлов),
  env-flag (отклонено — docs-reviewer автосоздаёт), diff-based (отклонено —
  сложнее, не гарантирует валидность).
- **`docs/project-map/README.md`** — секция Update Protocol дополнена строкой
  про новый guard; tree обновлён (новый тест-файл + тег PR#67 на
  pipeline-status.py).

## Почему

Детерминированный guard для project-map отсутствовал —
`pipeline-status.py:check_docs` проверял handoff, ADR и docs-reviewer
comment, но не валидировал `docs/project-map/`. Это позволяло PR проходить
DOCS-фазу даже при «тихом провале» docs-reviewer (забыл обновить карту при
структурных изменениях). Memory `technical/pipeline-status-no-project-map-check.md`
зафиксировала эту дыру.

Контракт карты был размазан по 3 файлам (`docs-reviewer.md`,
`get-project-map/SKILL.md`, `reviewer.md`) с несоответствиями (2 разных commit
message для одной операции). Фактическое состояние — только README.md без
frontmatter и модульных файлов. ADR-028 ослабляет контракт под фактическое
состояние (README.md mandatory, модульные файлы aspirational) и фиксирует
единый source of truth. Полный контракт остаётся в `get-project-map/SKILL.md`
как рекомендация.

Локальная верификация: 404 теста прошли, coverage 89.19% ≥ 80%
(pipeline-status.py покрыт на 88%).

## Pending

- После merge: фаза MEMORY — обновить memory
  `technical/pipeline-status-no-project-map-check.md` секцией Closure (guard
  добавлен в PR#67, контракт зафиксирован в ADR-028).
- ADR filename переименован с placeholder `<PR-NUMBER>` на `028-pr-67-project-map-contract.md`
  после create_pr (коммит `docs(handoff): set PR number`).

## Watch out

- **10 существующих тестов правлены** — `test_check_docs_*` в
  `test_pipeline_status.py` теперь требуют `make_project_map(tmp_path)` +
  monkeypatch `PROJECT_MAP_DIR`. Если добавить новый `test_check_docs_*` без
  этого — упадёт на NOT_DONE от `check_project_map`. Паттерн: копируй строку
  `monkeypatch.setattr(ps, "PROJECT_MAP_DIR", make_project_map(tmp_path))` из
  соседнего теста.
- **Уникальный module name в новом тесте** — `pipeline_status_project_map`
  (не `pipeline_status`). Если использовать `pipeline_status`, произойдёт
  коллизия с `test_pipeline_status.py`/`test_pipeline_status_adr.py` которые
  грузят скрипт под тем же именем — `sys.modules` уже занят, тесты
  конфликтуют. Шаблон: новый test-файл для под-проверки → уникальный module
  name.
- **Контракт ослаблен намеренно** — ADR-028 явно фиксирует что модульные
  файлы и frontmatter `last_updated` НЕ enforced. Если будущий PR захочет
  ужесточить контракт (полный enforcement) — это будет breaking change,
  требующий создания ~12 файлов для существующего репо. См. альтернативу в
  ADR-028.
- **scaffold-handoff.sh нумерация** — скрипт считает `ls docs/decisions |
  grep -E '^[0-9]{3}-' | wc -l + 1`. Если ADR-028 уже существует, следующий
  вызов даст 029. При создании ADR вручную + через scaffold в одном PR —
  scaffold создаст дубликат с +1 номером (нужно удалить вручную).