# ADR-026: Enforce tool-usage policy across prompts and skills

## Статус

Accepted (2026-07-25)

## Контекст

Research (subagent explore) выявил 3 gap'а в tool-usage инфраструктуре репо
`slaid098/opencode-config`:

### Gap A: Skills предписывают raw bash, заблокированный deny-правилами

6 skill-файлов предписывали subagent'ам raw bash команды, которые глобально
заблокированы в `opencode.json:311-314` (4 deny-правила: `git commit *`,
`gh pr create *`, `gh pr merge *`, `gh issue create *`):

- `run-pipeline/SKILL.md` Template A: `gh pr create`, `git commit`
- `run-pipeline/SKILL.md` Template B/D: `git commit`
- `issue/SKILL.md:83,127`: `gh issue create`; `:154`: `gh pr merge`
- `spec/SKILL.md:311`: `gh issue create`
- `release/SKILL.md:44`, `add-skill/SKILL.md:70`, `repo-init/SKILL.md:41`:
  `git commit`

Симптом: subagent грузит skill → получает инструкцию нарушить permission
layer → падает (deny блокирует bash) или импровизирует обход через `gh api`
(см. memory `technical/gh-issue-create-blocked-workaround.md` — `gh api
repos/*/issues` allow-rule существует, но использование его для создания
issue обходит валидацию тулзы и ломает формат репо).

### Gap B: Tool failure handling только для 2 из 10 tools

Секция "Tool failure handling" (инструкция "при сбое tool — STOP, не fallback
на raw bash") была только для `post_review` (`reviewer.md:285`) и
`post_docs_review` (`docs-reviewer.md:228`). Для `commit`, `create_pr`,
`create_issue`, `merge_pr`, `pipeline_status`, `spec_status`, `memory_setup`,
`tunnel` — отсутствует. При сбое agent без инструкции, импровизирует.

### Gap C: 4 tool'а не упомянуты нигде как tool

- `create_issue`, `create_pr` — 0 упоминаний как tool в промптах/skills
  (только raw `gh issue create`/`gh pr create` в skills, заблокированные deny)
- `tunnel` — skill удалён в PR#42 (orchestration-switch), tool остался orphan
  (0 упоминаний как tool; пользователь использует вручную, но агент не
  "видит" его в контексте)
- `memory_setup` — 1 строка в `memory/SKILL.md:19`, в промптах агентов нет

### Дополнительно

`gh pr comment*` остался в allow-list reviewer/docs-reviewer (ADR-019
отклонил strict-deny "для обратной совместимости"). Решено: оставить в allow
(промпт уже запрещает fallback при сбое tool, противоречие minimal).

## Решение

### 1. AGENTS.md: единая Tool Usage Policy секция

В `AGENTS.md` добавлена секция `## Tool Usage Policy` с таблицей всех 10
tools. Колонки: имя tool | raw-эквивалент (заблокирован deny) | когда
использовать | при сбое — STOP, репорт, НЕ fallback. Правило: "Используй tool
вместо raw bash. Raw bash-эквиваленты заблокированы deny
(`opencode.json:311-314`). При сбое tool — STOP и репорт оркестратору, НЕ
fallback на raw bash, НЕ импровизируй обход через `gh api`."

Это устраняет Gap B (единая policy для всех 10 tools вместо per-tool
инструкций) и Gap C (4 tool'а теперь упомянуты как tool в едином месте).

Global copy `/root/.config/opencode/AGENTS.md` — bind-mount read-only из
workspace `AGENTS.md` (compose: `./AGENTS.md:/root/.config/opencode/AGENTS.md:ro`).
В контейнере файл не редактируется; изменения идут в workspace copy, после
merge + host `git pull` + container restart подхватываются автоматически. Это
symlink-эквивалент через bind-mount.

### 2. 6 skills: raw bash → tool-вызовы

Все предписания raw bash в 6 skills заменены на tool-вызовы (см. handoff "Что
сделано" для полного списка). `git add` и `git push` оставлены — они не
заблокированы deny. Anti-instructions (упоминания "НЕ raw bash") сохранены
как документация.

### 3. Skill `tunnel` восстановлен

Создан `.opencode/skills/tunnel/SKILL.md` (удалён в PR#42, tool остался
orphan). Frontmatter: name: tunnel, description с триггерами "подними
тоннель"/"пробрось порт"/"tunnel". Тело: инструкция вызывать tool `tunnel()`
(1-й вызов — start, 2-й — stop, беспараметровый). Skill станет доступен
после рестарта opencode (skills загружаются при старте).

### 4. `gh pr comment*` оставлен в allow-list

Не трогали — intentional (ADR-019). Промпт уже запрещает fallback при сбое
tool (`post_review`/`post_docs_review`), противоречие minimal. strict-deny
`gh pr comment*` отклонён (см. Альтернативы).

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

- **Per-agent промпты: добавить tool failure handling в каждый agent.md
  по отдельности** — отклонено из-за дублирования. 4 agent-файла × 10 tools =
  40 упоминаний, дрейф при добавлении новых tools. Единая policy в AGENTS.md
  (грузится всеми агентами) — canonical источник, добавление нового tool =
  1 строка в таблице.

- **strict-deny `gh pr comment*` (принудить к `post_review`/`post_docs_review`)** —
  отклонено (повторяет ADR-019). `gh pr comment*` остаётся в allow-list для
  обратной совместимости; tools используют spawnSync напрямую (не через bash
  permission layer), raw `gh pr comment*` не конфликтует. Промпт уже запрещает
  fallback при сбое tool — этого достаточно.

- **Упомянуть 4 orphan tool'а (Gap C) в per-agent промптах** — отклонено по
  той же причине дублирования. Единая таблица в AGENTS.md покрывает все 10
  tools одним источником правды.

- **Удалить skill `tunnel` совсем (tool orphan → удалить tool)** — отклонено:
  tool `tunnel` используется пользователем вручную и в pipeline (tunnel.sh
  scaffold). Восстановление skill даёт агенту контекст для вызова tool'а
  по триггерам пользователя.