# ADR-028: Project map contract (mandatory README.md, aspirational modules)

## Статус

Accepted (2026-07-25)

## Контекст

Контракт `docs/project-map/` в репо `slaid098/opencode-config` размазан по 3
файлам с внутренними несоответствиями:

- `.opencode/agents/docs-reviewer.md:158-159,191` — обязывает
  docs-reviewer обновлять карту при структурных изменениях и предлагает
  один commit message.
- `.opencode/skills/get-project-map/SKILL.md:25-27` — описывает структуру
  карты (README.md + модульные файлы per module) и frontmatter
  `last_updated`.
- `.opencode/agents/reviewer.md:199-205` — требует от code-reviewer проверять
  актуальность карты и предлагает *другой* commit message для той же
  операции (несоответствие с docs-reviewer.md).

Фактическое состояние карты: только `README.md` (~140 строк, ASCII-дерево
структуры репо) без frontmatter и без модульных файлов. Полный контракт
(модульные файлы per module) никогда не соблюдался — aspirational документ,
не enforcement.

`pipeline-status.py:check_docs` (DOCS phase) валидировал handoff (4 секции),
ADR (filename pattern) и docs-reviewer comment (heading `## Docs Review
Summary`), но **не валидировал `docs/project-map/`**. Это позволяло PR проходить
DOCS-фазу даже при «тихом провале» docs-reviewer (забыл обновить карту при
структурных изменениях) — детерминированный guard отсутствовал. Зафиксировано
в memory `technical/pipeline-status-no-project-map-check.md`.

## Решение

Минимальный guard в `pipeline-status.py` — новая функция `check_project_map()`
проверяет существование `docs/project-map/README.md` (через
`PROJECT_MAP_DIR.exists()` + `README.md.exists()`). Встроена в `check_docs`
между `check_adr` и `_check_docs_reviewer_comment` (short-circuit на
NOT_DONE). Mandatory для pipeline-driven репо: docs-reviewer автосоздаёт
initial README.md при отсутствии каталога (`docs-reviewer.md:82-84`), поэтому
guard не блокирует первый PR и не требует ручной bootstrap-операции.

Контракт ослаблен под фактическое состояние:

- **README.md обязателен** — единственное, что проверяется детерминированно.
  Возвращает DONE с размером файла в detail, либо NOT_DONE с указанием что
  docs-reviewer должен создать (с ссылками на строки docs-reviewer.md).
- **Модульные файлы per module и frontmatter `last_updated`** — aspirational,
  НЕ enforced. Полный контракт остаётся в `get-project-map/SKILL.md` как
  рекомендация для docs-reviewer, но не блокирует пайплайн.

Возвращает только DONE/NOT_DONE (без AMBIGUOUS) — проверка файловой системы
детерминирована, нет внешних API вызовов.

## Альтернативы

- **Полный контракт (модульные файлы per module)** — отклонено: потребовало
  бы создания ~12 новых файлов (по одному на модуль) для существующего репо,
  блокируя пайплайн до их создания. Фактическое состояние никогда не
  соответствовало этому контракту — enforcement сломал бы все открытые PR.

- **Опциональность через env-flag `OPENCODE_REQUIRE_PROJECT_MAP`** —
  отклонено: docs-reviewer уже автосоздаёт initial README.md при отсутствии
  каталога (`docs-reviewer.md:82-84`), feature-flag избыточен — guard
  никогда не блокирует корректно настроенный pipeline-driven репо. Env-flag
  добавил бы конфигурационную поверхность без выгоды.

- **Diff-based проверка (карта обновлена в PR через `gh pr view --json
  files`)** — отклонено: сложнее в реализации (парсинг diff, фильтр по путям),
  не гарантирует валидность содержимого (файл может быть изменён, но остаться
  пустым/устаревшим). Существование README.md — более сильный guard (файл
  присутствует), diff-based — слабее (файл изменён, но может быть невалиден).