---
pr: 122
title: fix(pipeline-status): use statusCheckRollup instead of actions/runs CI filter
---

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

- **CI-проверка переведена с `gh api .../actions/runs` + `name=="CI"` фильтра
  на `gh pr view --json statusCheckRollup`** в `.opencode/scripts/pipeline-status.py`.
  `statusCheckRollup` агрегирует ВСЕ checks по head SHA PR (CI, CI (always),
  ADR check и т.д.) — не зависит от имени workflow.
- **Убраны функции**: `_get_pr_head_branch`, `_query_ci_run`,
  `_classify_ci_status`, `_poll_until_done`, `_retry_no_runs`,
  `_extract_json_field_loose` (использовалась только в CI-фазе).
- **Добавлены функции**: `_query_ci_rollup` (запрос rollup), `_classify_rollup`
  (классификация статусов/conclusions), `_classify_rollup_with_poll`
  (polling при IN_PROGRESS/QUEUED/PENDING), `_retry_no_checks` (ререй при
  пустом rollup).
- **Тесты переписаны** (`tests/test_pipeline_status_ci.py`): 23 теста под
  statusCheckRollup, включая ключевой `test_ci_docs_only_pr` (CI (always) +
  ADR check без ci.yml — regression guard для PR#120 bug). Mock-фабрика
  `mock_run_cmd_seq` теперь принимает `pr_view_responses` вместо `api_responses`.
- **Docstring модуля** обновлён: CI-фаза описана через statusCheckRollup.
- **Handoff + ADR-054** созданы.

Проверки: `ruff check` OK; `ruff format --check` OK (40 files);
`pytest tests/test_pipeline_status_ci.py tests/test_pipeline_status.py` 107
passed; `check-permissions.py` OK.

## Почему

Docs-only PR (PR#120) не запускают `ci.yml` (paths-ignore), только
`always-ci.yml`. Старый код фильтровал workflow runs по `name=="CI"` → не
находил `CI (always)` → возвращал AMBIGUOUS ("нет CI run на ветке ...").
Pipeline застревал на CI-фазе. `statusCheckRollup` агрегирует все checks по
head SHA — не зависит от имени workflow, корректно обрабатывает docs-only PR.

## Pending

—

## Watch out

- `_extract_json_field_loose` убрана — использовалась ТОЛЬКО в CI-фазе.
  `extract_json_field` (без `_loose`) оставлена — используется в `check_merge`
  и `get_pr_title`.
- `JSON_FIELD_RE` (модульная константа) не используется нигде — оставлена как
  есть (не входит в scope issue #121, была не используется и до PR).
- `_classify_rollup` считает `SKIPPED` и `NEUTRAL` conclusions успешными
  (вместе с `SUCCESS`) — GitHub помечает skipped jobs как SKIPPED, это не
  failure.
- `_classify_rollup` использует regex для извлечения `status`/`conclusion`
  из JSON (без `json` импорта) — стиль скрипта (см. `extract_json_field`).
  Если в JSON появятся поля с такими же именами вне `statusCheckRollup` —
  возможны ложные matchи; но `gh pr view --json statusCheckRollup` возвращает
  только rollup, так что на практике безопасно.
- Polling сообщение изменилось: было `gh run view --branch {head_branch}`,
  стало `gh pr checks {pr_number}`.