doctor · diff
git:20260911.1ea0673 to git:20261008.46339a7
128 added, 36 removed. Audit A to A.
---
name: doctor
description: Diagnose Polisade Orchestrator project health
---
# /polisade:doctor — Project Health Diagnostics
+ **Первое действие при `$ARGUMENTS` с `--help` или `-h`:** выполнить ровно
+ эту команду, показать её stdout и завершить `/polisade:doctor`:
+
+ ```bash
+ env ${POLISADE_PYTHON:-python3} {plugin_root}/scripts/polisade_doctor.py --help
+ ```
+
+ <!-- polisade:exec-denied CAPSULE BEGIN -->
+ > ⛔ **Если вызов скрипта на этом шаге ты СДЕЛАЛ, и он отклонён или не запустился** (`Command references protected path` / `Install directory is read-protected` / `Filesystem Guard` / `the tool's default permission is 'deny'`, отказ песочницы, ненулевой exit без вывода) — **STOP**. Вызова не было — отказа нет: сначала выполни команду; этот блок описывает её вывод и сам ответом не служит.
+ > Процитируй отказ дословно. НЕ пересказывай по исходнику, что скрипт «сделал бы»; НЕ собирай dry-run вручную; НЕ переходи к apply/push/pr-create.
+ > НЕ транскрибируй скрипт (прочитать → записать копию в `/tmp` или в проект → запустить копию): копия не байт-идентична — уезжают классы символов в regex, форма возврата функций, пропадают целые функции — и молча меняется набор применённых изменений.
+ > Отказ инструмента — это отказ, а не результат. Доложи PM дословный текст отказа и назови следствие: без рабочего вызова скрипта этот шаг выполнить нечем. Причину не придумывай — её называет только сам текст отказа.
+ <!-- polisade:exec-denied CAPSULE END -->
+
+ Справка не требует чтения проекта или запуска проверок. Используй указанный
+ shell-путь к скрипту, не подменяя его путём исходного чекаута.
+
Read-only диагностика здоровья Polisade Orchestrator-проекта. Проверяет структуру, файлы состояния, инструменты и консистентность.
- **Status vocabulary check** (`artifact_statuses`, issue #151): статус артефакта
+ **Status vocabulary check** (`artifact_statuses`): статус артефакта
вне закрытого словаря (`scripts/_polisade_state_model.py`) — почти всегда опечатка,
и она молча выкидывает артефакт из ВСЕХ производных списков `PROJECT_STATE.json`
(в `artifactIndex` он при этом остаётся). Проверка даёт WARN и перечисляет
нарушителей; переходы статусов нигде не enforce'ятся — это тонкий stdlib-клиент.
+ Также WARN для `cancelled`/`not_actual`/`not_applicable` без
+ `status_reason` и для `not_actual` вне BUG. Зелёный `artifact_statuses`
+ означает только корректную форму, а не доказанную причину или merge PR.
+ `adr_outcome_lists` отдельно предупреждает, если ADR `not_applicable`
+ по-прежнему числится в `architecture.activeADRs` или `deprecatedADRs`.
- **Pre-push hook check** (`prepush_hook`, issue #159): установлен ли git-хук
+ **Pre-push hook check** (`prepush_hook`): установлен ли git-хук
`/polisade:init`, запрещающий push в `main`/`master` без
`POLISADE_ALLOW_MAIN_PUSH=1` и push ветки, отличной от `POLISADE_EXPECTED_BRANCH`
(когда та задана). Каталог хуков резолвится через `git rev-parse --git-path hooks`
— `core.hooksPath` и worktree учитываются. WARN, а не FAIL: хуки не клонируются,
не версионируются и обходятся `--no-verify`, так что это ремень безопасности перед
серверной защитой веток, а не она сама. В сообщении WARN — готовая команда
установки; чужой существующий `pre-push` doctor не трогает и не предлагает
перезаписать.
- **Vendored scripts check** (`scripts_vendor`, issue #127): в сборках, где
+ **Vendored scripts check** (`scripts_vendor`): в сборках, где
Python-скрипты плагина исполняются из копии в проекте (`.polisade/bin`, потому
что каталог установки закрыт Filesystem Guard'ом), сверяет копию с
`.polisade/bin/MANIFEST.sha256` — версия плагина плюс sha256 каждого файла.
Отсутствие копии под такой сборкой — FAIL с командой установки; нехватка файлов
или несовпавший хэш — FAIL «копия устарела или искажена»; версия манифеста
старше версии проекта — WARN. Отличие ТОЛЬКО в переводе строки (CRLF) —
отдельный WARN с причиной (`git core.autocrlf`), а не «искажена»: содержимое то
же. Права на файлы не проверяются — хэш считается по байтам. Если `.polisade/bin`
нет и сборка не вендорит скрипты, проверка PASS'ит как неприменимая. Сборка
определяется тремя ИЛИ-сигналами, ни один из которых не читает каталог
установки: `POLISADE_PLUGIN_ROOT` с `.gigacode/`, заголовок `# target: gigacode`
в манифесте, либо `GIGACODE.md` как ЕДИНСТВЕННЫЙ контекстный файл проекта.
**VCS provider check** (встроенный в дефолтный отчёт + отдельный режим `--vcs`):
- под сборкой GigaCode `vcsProvider: github` даёт WARN (issue #120): GitHub
+ под сборкой GigaCode `vcsProvider: github` даёт WARN: GitHub
недоступен по сети этой инсталляции, дефолт там — `bitbucket-server`. Если `settings.vcsProvider == "bitbucket-server"` — проверяет наличие `.env`, что хотя бы один `BITBUCKET_DOMAIN{1,2}_URL` и `_TOKEN` заполнены (не stub-значения), что хост `git remote origin` совпадает с одним из заполненных доменов, и что `whoami` через `polisade_vcs.py` к матчнувшемуся инстансу возвращает 200 (через аутентифицированный endpoint — невалидный токен даст 401).
+ **CLI checks in the full report:** `gh_auth` запускает `gh auth status` только
+ при `vcsProvider: github`; при `bitbucket-server` пишет `not applicable` и
+ отсылает к `vcs_provider`, не утверждая, что GitHub-аутентификация проверена.
+ `reviewer_cli` использует тот же резолвер, что review-команды, и настройки
+ `settings.reviewer`: выбранный Codex требует рабочий CLI; при доступном
+ штатном self-review отсутствие необязательного Codex не краснит doctor.
+ Явный недоступный reviewer и отсутствие обоих reviewer-путей дают FAIL.
+ Строка проверки CLI не доказывает аутентификацию reviewer.
+
## Использование
```
/polisade:doctor # Диагностика текущего проекта (включая vcs_provider)
+ /polisade:doctor --format=json # Тот же отчёт как JSON — для CI и скриптов
/polisade:doctor --traceability # Traceability matrix report (text)
/polisade:doctor --traceability --format=md # Markdown table
/polisade:doctor --traceability --format=json # JSON для CI
/polisade:doctor --questions # Open questions across all artifacts
/polisade:doctor --questions --format=json # JSON для автоматизации
/polisade:doctor --vcs # Только VCS-провайдер (быстрая диагностика токена/хоста)
/polisade:doctor --vcs --format=json # JSON для автоматизации
+ /polisade:doctor --help # Справка CLI без проверок проекта
```
+ Поддерживаемые режимы скрипта: `--traceability`, `--questions`,
+ `--architecture`, `--vcs`, `--cli-caps`, `--verify-scripts`. `--format=md`
+ применим к полному отчёту, `--traceability` и `--questions`; остальные
+ отдельные режимы принимают `text` или `json`. Полный health-отчёт **без
+ `--format` остаётся JSON** (на него завязаны потребители), а `--format=text`
+ печатает тот самый box — его рисует ИНСТРУМЕНТ, не модель. Имена
+ `--state-schema`, `--artifacts`, `--hooks`, `--design` **не являются**
+ селекторами: при них нужно показать ошибку и справку, а не запускать все
+ проверки. Отдельные проверки `state_schema`, `artifact_sync`, `prepush_hook`,
+ `design_packages` остаются в полном отчёте. Скрипт принимает старый `--json`
+ как синоним `--format=json`.
+
+ `corpus_mode` различает **три** состояния архитектурной документации, а не
+ два: режим живого корпуса выключен (архитектура ведётся силосами — это
+ нормальный путь, PASS); режим включён, а корпуса нет (`manifest.yaml`
+ отсутствует — **WARN** с готовой командой: корпус не строится сам); корпус
+ живой (PASS, и отдельно называются ещё не переведённые силосы). Печатай
+ сообщение дословно вместе с командой из него — маршрут к корпусу PM получает
+ отсюда, а не из исходников. Это НЕ то же, что `corpus_review`: та считает
+ сгенерированные файлы, которых ещё не читал человек, и на проекте без корпуса
+ честно отвечает «корпуса нет» — вопрос «а режим-то включён?» ей не задавали.
+
+ `design_packages` собирает DESIGN-пакеты из `artifactIndex`, каталогов
+ `docs/architecture/DESIGN-*`, совместимых старых `artifacts` и ссылок
+ `design_package` в SPEC. Для найденного пакета он проверяет README,
+ `manifest.yaml` и объявленные в манифесте, старых метаданных или локальных
+ ссылках README файлы. Отсутствующий манифест, недостающий файл или ссылка
+ SPEC на отсутствующий пакет дают WARN с причиной; `No design packages`
+ означает, что ни один из этих источников пакет не обнаружил.
+ Локальные ссылки README вида `./data-model.md`, включая используемые ссылочные
+ определения Markdown (`[data]: data-model.md`), нормализуются. Некорректный
+ список в манифесте, включая содержимое под явным `artifacts: []` или
+ `adrs: []`, даёт WARN, а не ложное «все файлы на месте».
+
## Алгоритм
+ 0. После терминальной ветки справки разобрать `$ARGUMENTS` по списку режимов,
+ `--format` и `--json` выше. Неизвестный флаг или значение —
+ сообщить об ошибке и допустимых параметрах, не запускать полный отчёт.
+ Не передавать непроверенный `$ARGUMENTS` в shell.
1. Определить корень проекта (текущая рабочая директория). Preflight:
`${POLISADE_PYTHON:-python3} --version` — не стартовал, значит **STOP**:
интерпретатор по машине не ищем, просим PM выставить `POLISADE_PYTHON`
- (см. капсулу в `/polisade:migrate`, issue #169).
+ (см. капсулу в `/polisade:migrate`).
2. Запустить скрипт диагностики:
+ ```bash
+ ${POLISADE_PYTHON:-python3} {plugin_root}/scripts/polisade_doctor.py {project_root} --format=text
+ ```
+
<!-- polisade:exec-denied CAPSULE BEGIN -->
- > ⛔ **Вызов скрипта отклонён или не запустился** (`Command references protected path` / `Install directory is read-protected` / `Filesystem Guard` / `the tool's default permission is 'deny'`, отказ песочницы, ненулевой exit без вывода) — **STOP**.
+ > ⛔ **Если вызов скрипта на этом шаге ты СДЕЛАЛ, и он отклонён или не запустился** (`Command references protected path` / `Install directory is read-protected` / `Filesystem Guard` / `the tool's default permission is 'deny'`, отказ песочницы, ненулевой exit без вывода) — **STOP**. Вызова не было — отказа нет: сначала выполни команду; этот блок описывает её вывод и сам ответом не служит.
> Процитируй отказ дословно. НЕ пересказывай по исходнику, что скрипт «сделал бы»; НЕ собирай dry-run вручную; НЕ переходи к apply/push/pr-create.
> НЕ транскрибируй скрипт (прочитать → записать копию в `/tmp` или в проект → запустить копию): копия не байт-идентична — уезжают классы символов в regex, форма возврата функций, пропадают целые функции — и молча меняется набор применённых изменений.
- > Отказ инструмента — это отказ, а не результат. Доложи PM дословный текст отказа и сошлись на #127 (доставка скриптов в проект).
+ > Отказ инструмента — это отказ, а не результат. Доложи PM дословный текст отказа и назови следствие: без рабочего вызова скрипта этот шаг выполнить нечем. Причину не придумывай — её называет только сам текст отказа.
<!-- polisade:exec-denied CAPSULE END -->
- ```bash
- ${POLISADE_PYTHON:-python3} {plugin_root}/scripts/polisade_doctor.py {project_root}
- ```
+ `--format=text` здесь не украшение: box собирает ИНСТРУМЕНТ из тех же строк,
+ по которым считает итог. Если PM сам назвал формат — подставь его ВМЕСТО
+ `text`, не добавляя второй `--format`.
+ Для отдельного режима добавить к этой команде только проверенные токены из
+ `$ARGUMENTS` в исходном порядке, например `--questions --format=json`.
+ Скрипт сам отвергает неизвестные опции до чтения проекта.
+
Где `{plugin_root}` — корень Polisade Orchestrator плагина (директория, содержащая `scripts/`).
Команду выше бери как есть: в сборках, где скрипты вендорятся в проект
- (`.polisade/bin`, issue #127), путь к скрипту уже подставлен конвертером — не
+ (`.polisade/bin`), путь к скрипту уже подставлен конвертером — не
подменяй его на корень плагина.
- 3. Распарсить JSON-ответ скрипта.
- 4. Вывести результат в box-формате.
+ 3. **Доложить PM итог и путь к полному отчёту — готовыми строками
+ инструмента.** Последние две строки вывода скрипта:
+ `Итог doctor: FAIL <число> — <имена>; WARN <число> — <имена>; PASS <число>.`
+ и `Полный отчёт для PM сохранён в файл: <путь>`. **Покажи PM обе строки
+ как есть, дословно.** Перечень FAIL и WARN в строке итога собрал
+ ИНСТРУМЕНТ тем же проходом, что и числа; свой перечень не составляй, имена
+ между группами не переноси и не добавляй. Подробность по проверке — её
+ строка из box'а целиком, с ТЕМ ЖЕ тегом. ⛔ Замерено на живых прогонах:
+ собранный моделью перечень назвал одну проверку и FAIL, и (лишней
+ строкой) WARN — строк WARN стало больше, чем в итоге; раньше пройденная
+ проверка приехала к PM как провалившаяся. Перевёрнутый вердикт хуже
+ выдуманного числа: число PM перепроверит, а по FAIL он действует. Строки
+ итога в выводе нет — значит, вызова не было или он не дошёл до конца:
+ скажи это, итог не составляй. Строка о файле говорит «НЕ сохранён» —
+ скажи это прямо: тогда box выше единственная копия.
+ 4. Если PM просил JSON (`--format=json`) — печатать stdout скрипта как есть,
+ тоже без пересборки.
## Формат вывода
+ Форма вывода `--format=text` (заполнители в угловых скобках; имена,
+ статусы и числа печатает ТОЛЬКО инструмент — проверки называются
+ `project_state`, `counters`, `counter_shared`, `seed_identity`, `templates`,
+ `corpus_mode`, `artifact_sync`, `prepush_hook`, `scripts_vendor`, `pm_reports`
+ и другие):
+
```
═══════════════════════════════════════════
Polisade Orchestrator DOCTOR
═══════════════════════════════════════════
- [PASS] project_state — .state/PROJECT_STATE.json
- [PASS] counters — .state/counters.json
- [PASS] counter_shared — .state/counters.json ездит с репозиторием
- [PASS] seed_identity — 2 артефакта под семенем; номера выдаёт /polisade:sync на транке
- [PASS] knowledge — .state/knowledge.json
- [PASS] templates — 9 templates found
- [PASS] backlog_dir — backlog/
- [PASS] tasks_dir — tasks/
- [PASS] architecture_dir — docs/architecture/
- [PASS] gh_auth — Logged in as user
- [FAIL] codex_cli — Command not found: codex
- [PASS] state_schema — v2.8.1, schema 2
- [WARN] artifact_sync — Orphan files: TASK-005
- [PASS] design_packages — 2 design packages, all files present
- [PASS] artifact_statuses — all artifact statuses in the known vocabulary
- [WARN] prepush_hook — .git/hooks/pre-push not found. install: ...
- [PASS] scripts_vendor — .polisade/bin: 22 файл(ов) совпали с MANIFEST.sha256 (версия 3.7.6)
+ <[PASS] | [WARN] | [FAIL]> <имя проверки> — <сообщение проверки>
+ …
───────────────────────────────────────────
- Summary: 8 pass, 1 warn, 1 fail
+ Summary: <число> pass, <число> warn, <число> fail
+ Итог doctor: FAIL <число> — <имена>; WARN <число> — <имена>; PASS <число>.
═══════════════════════════════════════════
```
+ Группа без проверок печатается без перечня: `FAIL 0; …`. Тем же значением
+ строка итога лежит в поле `verdict_line` JSON (`--format=json`) и в
+ `.state/pm-reports/doctor.latest.json`.
+
## Traceability Matrix
<!-- polisade:silo-legacy POINTER — канон «Силос → корпус» живёт в /polisade:design -->
> **Силос ≠ корпус.** Источник правды по архитектуре — живой корпус
> `docs/architecture/`; пакет `DESIGN-NNN-<slug>/` — legacy-силос. Прочитал
> файл из силоса — скажи об этом вслух (переходное чтение). Полный канон —
> `/polisade:design`, блок «Силос → корпус»; перевод силоса на корпус —
> `${POLISADE_PYTHON:-python3} scripts/polisade_migrate_silo.py <пакет>` (dry-run по умолчанию).
Режим `--traceability` строит матрицу прослеживаемости требований:
```
PRD/SPEC/FEAT FR/NFR → DESIGN sub-artifacts (realizes_requirements) → TASK (requirements:)
```
Парсит:
- `docs/prd/PRD-*.md`, `docs/specs/SPEC-*.md`, `backlog/features/FEAT-*.md` — FR-NNN (### headings) и NFR-NNN (table rows)
- - `docs/architecture/DESIGN-*/manifest.yaml` — `realizes_requirements` и ADR `addresses`
+ - `docs/architecture/DESIGN-*/manifest.yaml` — `realizes_requirements` и ADR `addresses`; статус ADR проверяется по самому файлу решения, включая legacy `docs/adr/`
- `tasks/TASK-*.md` — `requirements:` frontmatter + `status:`
IDs в матрице приводятся к composite формату `{DOC}.FR-NNN` — `PRD-001.FR-007` и `FEAT-002.FR-007` показываются в отдельных секциях и никогда не сливаются, даже если совпадает номер.
Пример вывода:
```
════════════════════════════════════════════════════════════
TRACEABILITY MATRIX
════════════════════════════════════════════════════════════
⚠️ AMBIGUOUS REFERENCES DETECTED
────────────────────────────────────────────────────────────
FR-007: defined in PRD-001, FEAT-002
bare ref at tasks/TASK-003-foo.md (as `FR-07`)
Run /polisade:migrate --apply to attach scope prefixes automatically.
────────────────────────────────────────────────────────────
SPEC-001 → DESIGN-001
Requirement Realized in DESIGN Tasks Status
──────────────────── ──────────────────────────── ──────────────────── ────────────────
SPEC-001.FR-001 api.md, c4-container.md TASK-001, TASK-005 done
SPEC-001.FR-002 api.md TASK-002 review
SPEC-001.FR-003 (none) (none) ❌ NOT COVERED
SPEC-001.NFR-001 quality-scenarios.md TASK-008 done
SPEC-001.NFR-002 ADR-001 (none) ⚠️ NO TASK
────────────────────────────────────────────────────────────
Total: 5 (3 FR + 2 NFR)
Coverage: 4/5 (80%)
Realized in design: 4/5
Has tasks: 3/5
Done: 2/5
Not covered: SPEC-001.FR-003
════════════════════════════════════════════════════════════
```
Секция «AMBIGUOUS REFERENCES» — **non-blocking warning**: она появляется, когда один и тот же FR/NFR объявлен в >1 top-level документе И хотя бы одна cross-doc ссылка сделана bare. Сама по себе она не меняет exit code — блокировка ambiguous refs это работа `polisade_lint_artifacts.py`.
- Exit code: 0 если все требования покрыты (design или tasks), 1 если есть uncovered. Breaking change в v2.22.0: JSON root — теперь объект `{"matrix": [...], "ambiguous_refs": [...]}` вместо массива (читай `result.matrix[]` вместо `result[]`).
+ Exit code: 0 если все требования покрыты (design или неотменёнными tasks), 1 если есть uncovered. `cancelled`/`not_actual` TASK остаются видны с `status_reason`, но не дают покрытие или `done`; сводка отдельно считает `with_non_success_tasks` — число требований, где есть такие TASK, даже если у требования есть и реализованная TASK. ADR со статусом `not_applicable` и его `status_reason` выводятся отдельно как `non_applicable_adrs`, не попадают в `realized_in` и не дают покрытие. Статус берётся из файла ADR; если файл отсутствует, учитывается явный статус в манифесте. `with_non_applicable_adrs` считает требования с такими ADR, даже если требование покрыто другим артефактом. Breaking change в v2.22.0: JSON root — теперь объект `{"matrix": [...], "ambiguous_refs": [...]}` вместо массива (читай `result.matrix[]` вместо `result[]`).
## Важно
- - **Read-only** — ничего не модифицирует
+ - **Read-only для проекта** — пишет только файл отчёта для PM в
+ `.state/pm-reports/` (локальный, в git не попадает); проверка `pm_reports`
+ читает оттуда последние отчёты `migrate`/`sync`
- Для исправления drift используй `/polisade:sync`
- Для исправления schema warnings используй `/polisade:migrate`
- - Когда `/polisade:doctor` советует `/polisade:sync` или `/polisade:migrate` — после `--apply` смотри раздел «После применения — закоммить и открыть PR» в этих скиллах (issue #108): canonical 7-шаговый рецепт довоза diff'а до PR через `polisade_vcs.py git-push` + `polisade_vcs.py pr-create --body-file`.
- - Для установки Codex CLI: `npm install -g @openai/codex` ИЛИ `brew install openai-codex` (документация: https://github.com/openai/codex)
+ - Когда `/polisade:doctor` советует `/polisade:sync` или `/polisade:migrate` — после `--apply` смотри раздел «После применения — закоммить и открыть PR» в этих скиллах: canonical 7-шаговый рецепт довоза diff'а до PR через `polisade_vcs.py git-push` + `polisade_vcs.py pr-create --body-file`.
+ - Если явно выбран Codex reviewer и `reviewer_cli` красный: установить Codex CLI (`npm install -g @openai/codex` или `brew install openai-codex`; документация: https://github.com/openai/codex) либо исправить `settings.reviewer`.