---
name: pr
description: 'Provider-agnostic PR operations (GitHub / Bitbucket Server) for PM — create, list, view, diff, merge, comment, close, whoami. Use when PM mentions "open a PR", "create pull request", "push and open PR", "submit for review", "commit and open PR", "merge PR", "review PR status", "закоммить и сделай pr", "сделай пиар", "сделай pr", or any request to operate on GitHub / Bitbucket Server pull requests. Trigger liberally — skipping forces the agent to improvise with ad-hoc REST calls that bypass OPS-028 push verification; over-triggering is recoverable (PM can redirect).'
argument-hint: <subcommand> [args]
---

# /polisade:pr — Provider-Agnostic PR Operations

Обёртка над `scripts/polisade_vcs.py` для ручных операций с PR: просмотр списка, диффа, комментарии, merge, close. Провайдер определяется из `.state/PROJECT_STATE.json → settings.vcsProvider` (`github` по умолчанию, `bitbucket-server` при корпоративном self-hosted Bitbucket).

Перед первым вызовом для Bitbucket заполни `.env` (BITBUCKET_DOMAIN1/2_URL + TOKEN) — подсказка в `env.example`, валидация через `/polisade:doctor`.

## Использование

```
/polisade:pr create --title T (--body B | --body-file F | --body-stdin) [--head BR] [--base main]
/polisade:pr list [--head BRANCH] [--state OPEN|MERGED|ALL]
/polisade:pr view <id>
/polisade:pr diff <id>
/polisade:pr merge <id> [--squash] [--delete-branch]
/polisade:pr comment <id> (--body T | --body-file F | --body-stdin)
/polisade:pr close <id>
/polisade:pr whoami
```

<!-- 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 дословный текст отказа и сошлись на #127 (доставка скриптов в проект).
<!-- polisade:exec-denied CAPSULE END -->

Имена параметров соответствуют argparse в `scripts/polisade_vcs.py` — единственный source of truth. Проверь: `${POLISADE_PYTHON:-python3} {plugin_root}/scripts/polisade_vcs.py --help`.

## Алгоритм

1. Распарсить `$ARGUMENTS` как `<subcommand> [args]`.
2. Смаппить короткую форму субкоманды на имя скрипта:

   ```
   create  → pr-create
   list    → pr-list
   view    → pr-view
   diff    → pr-diff
   merge   → pr-merge
   comment → pr-comment
   close   → pr-close
   whoami  → whoami           (остаётся как есть)
   ```

3. Определить `WORK_DIR = ${POLISADE_WORK_DIR:-.}` — если вызов из worktree, скрипт должен читать локальный `.state/PROJECT_STATE.json` и `.env`. Caller (агент) обязан заранее `cd` в нужный каталог; статический fallback `.` намеренно не использует command substitution — корп-шелл (GigaCode CLI / codex sandbox) режет `$()` (см. anti-patterns ниже).
4. Выполнить:

<!-- 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 дословный текст отказа и сошлись на #127 (доставка скриптов в проект).
<!-- polisade:exec-denied CAPSULE END -->
<!-- polisade:push-stop CAPSULE BEGIN -->
> ⛔ **`polisade_vcs.py` недоступен** (`Command references protected path` / `Install directory is read-protected` / `Filesystem Guard`) — **STOP до push**: ни `git-push`, ни `pr-create`, ни `pr-merge`, ни `pr-comment` не выполняются.
> Bare `git push` запрещён (инвариант #10 / OPS-028); самодельные REST/curl-вызовы к Bitbucket/GitHub запрещены; helper не транскрибируется в `/tmp`.
> Доложи PM дословно: «push пропущен — `polisade_vcs.py` заблокирован sandbox (#127); коммит локально в ветке `<имя>`; pr-create не выполнялся» — и заверши рецепт на этом.
<!-- polisade:push-stop CAPSULE END -->

```bash
${POLISADE_PYTHON:-python3} {plugin_root}/scripts/polisade_vcs.py <script-cmd> <args> \
  --project-root "${POLISADE_WORK_DIR:-.}"
```

5. Человекочитаемо отформатировать результат:
   - `create` → номер + URL + head branch. **Поле `existing` (issue #158):**
     `pr-create` идемпотентен — если по head-ветке уже открыт PR, скрипт
     возвращает ЕГО в том же JSON-контракте с `existing: true` и exit 0,
     вместо того чтобы открыть дубль. Свежесозданный PR приходит с
     `existing: false`. Различай в отчёте PM («PR уже открыт: #N» vs «PR
     создан: #N») — молча выдать чужой номер за свежий значит соврать.
     Идемпотентность считает только ОТКРЫТЫЕ PR: смерженный или закрытый PR
     на той же ветке созданию не мешает.
   - `list` → таблица (number, head, state).
   - `view` → ключевые поля + URL.
   - `diff` → stdout как есть (уже text).
   - `merge` / `close` / `comment` — краткая сводка (`#N <state>`, `branch_deleted: yes/no`, warnings если есть).
   - `whoami` — инстанс и провайдер.
6. На non-zero exit показать stderr + подсказку: `запусти /polisade:doctor для диагностики VCS`.

## Частые ошибки

<!-- 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 дословный текст отказа и сошлись на #127 (доставка скриптов в проект).
<!-- polisade:exec-denied CAPSULE END -->
<!-- polisade:push-stop CAPSULE BEGIN -->
> ⛔ **`polisade_vcs.py` недоступен** (`Command references protected path` / `Install directory is read-protected` / `Filesystem Guard`) — **STOP до push**: ни `git-push`, ни `pr-create`, ни `pr-merge`, ни `pr-comment` не выполняются.
> Bare `git push` запрещён (инвариант #10 / OPS-028); самодельные REST/curl-вызовы к Bitbucket/GitHub запрещены; helper не транскрибируется в `/tmp`.
> Доложи PM дословно: «push пропущен — `polisade_vcs.py` заблокирован sandbox (#127); коммит локально в ветке `<имя>`; pr-create не выполнялся» — и заверши рецепт на этом.
<!-- polisade:push-stop CAPSULE END -->

- ❌ `polisade_vcs.py create` — подкоманда называется `pr-create` (префикс `pr-` обязателен для всех PR-операций кроме `whoami`).
- ❌ `--source-branch` / `--target-branch` / `--description` — это GitHub REST API; наш скрипт принимает `--head` / `--base` / `--body` (см. `--help`).
- ❌ Однострочный `--body "..."` с кавычками внутри текста → ломает shell quoting. Для многострочных тел используй `--body-file .polisade/tmp/body.md` или `--body-stdin`. `/tmp` НЕ используется: GigaCode CLI sandboxes /tmp, и файл становится невидим последующему `--body-file` (issue #57; project-local `.polisade/tmp/` — в `.gitignore`).
- ⛔ NEVER `--body "$(git log -1 --pretty=%B)"` / `--body \`...\`` / `--body <(...)` / `--body >(...)` — корп-шелл (GigaCode CLI, codex sandbox) режет любую command substitution с сообщением «Command substitution using $(), \`\`, <(), or >() is not allowed for security reasons». Запрет шире, чем уже декларированный «однострочный --body с кавычками»: тут нельзя сам shell-construct, не только многострочное body. Канонический путь — файл: `git log -1 --pretty=%B > .polisade/tmp/pr-body.md && /polisade:pr create --body-file .polisade/tmp/pr-body.md ...`. Issue #108 / OPS-057.
- ⛔ NEVER самодельный Python/curl в Bitbucket/GitHub REST API: `requests.post(".../pull-requests", json=...)`, чтение `BITBUCKET_DOMAIN1_TOKEN` / `BITBUCKET_DOMAIN2_TOKEN` через `subprocess` или `os.environ`, прямой `curl -X POST` к API. Это weak-model footgun: токены утекают мимо `polisade_vcs.py`, теряется OPS-028 push verification, теряется provider-agnostic мост (GitHub vs Bitbucket Server vs корпоративный фронт). Любой PR-flow проходит через `${POLISADE_PYTHON:-python3} {plugin_root}/scripts/polisade_vcs.py pr-create ...` — даже когда «всё равно нужно одну строчку отправить». Issue #108 (PM в корп-сессии отменил такой ad-hoc subprocess вручную).
- ⛔ NEVER `polisade_pr.py` / `${POLISADE_PYTHON:-python3} scripts/polisade_pr.py` — такого файла НЕ существует. Канонический скрипт — `scripts/polisade_vcs.py` с подкомандами `pr-create / pr-list / pr-view / pr-diff / pr-merge / pr-comment / pr-close / whoami`. Source-of-truth list — `${POLISADE_PYTHON:-python3} {plugin_root}/scripts/polisade_vcs.py --help`. Эта путаница `/polisade:pr ↔ polisade_pr.py` — тоже регрессия из issue #108: weak-model агент пробовал угадать имя файла из имени слаш-команды.

Source of truth для имён subparser'ов и параметров: `${POLISADE_PYTHON:-python3} {plugin_root}/scripts/polisade_vcs.py --help`.

## Важно

- **Merge собственных PR, созданных в автоцикле, делает PM** — автоматические merge происходят только в `/polisade:continue` после успешного review.
- **Close / decline** безвозвратно закрывает PR (Bitbucket: переводит в `DECLINED`, GitHub: `CLOSED`).
- Длинные тела для `comment` / `create` удобнее передавать через `--body-file`, а не `--body "..."` — кавычки внутри текста ломают quoting.
- ⛔ NEVER `git add -f <path>` / `git add --force <path>` перед `/polisade:pr create`
  на путях, которые в `.gitignore` (`.gigacode/`, `.qwen/`, `.codex/`,
  `.worktrees/` и т.п.). `/polisade:pr` сам `git add` не выполняет, но если PM
  перед вызовом собрал коммит с force-add'ом gitignored-пути, это тот же
  weak-model footgun (issue #74). Полные правила — в `## Git Safety` в
  `CLAUDE.md` target-проекта и в `skills/implement/SKILL.md`.

### Пример: создать PR с многострочным body

```bash
mkdir -p .polisade/tmp
cat > .polisade/tmp/pr-body.md <<'EOF'
## Summary
Fixes TASK-X.

## Tests
- pytest tests/foo -k new_case
EOF
/polisade:pr create --title "[TASK-X] Foo bug" --body-file .polisade/tmp/pr-body.md --head feat/TASK-X
```

## Настройка Bitbucket Server

См. раздел «VCS providers» в `CLAUDE.md`. Короткая версия:

1. `/polisade:init` или `/polisade:migrate --apply` создаст `.env` (stub) и `.env.example` из plugin templates.
2. Заполни в `.env` хотя бы один домен: `BITBUCKET_DOMAIN1_URL` + `BITBUCKET_DOMAIN1_TOKEN` (auth_type `bearer` по умолчанию; `basic` — при 401).
3. `/polisade:pr whoami` — проверка что токен валиден и инстанс выбран правильно.
4. `/polisade:doctor --vcs` — полная диагностика.
