diff --git a/TODO.md b/TODO.md index 369e138..a02e32a 100644 --- a/TODO.md +++ b/TODO.md @@ -11,15 +11,7 @@ Связанный PR: [anomalyco/opencode#16525](https://github.com/anomalyco/opencode/pull/16525) -## codex-review: убрать permission prompts +## ~~codex-review: убрать permission prompts~~ DONE -Скилл `codex-review` (`agents/.agents/skills/codex-review/SKILL.md`) вызывает -safety warnings Claude Code на `$()` и `>` в bash-командах. Allow-list не помогает — -это отдельный слой проверок. - -**Решение:** переписать шаги скилла, заменив bash-паттерны на встроенные инструменты: - -- **Шаг 2 (Session ID):** Claude генерирует ID сам, без `$(date)` в bash. -- **Шаг 3 (Diff):** `git diff` → результат через Write tool вместо `>` redirect. -- **Шаг 7 (Resume):** stdout tool result вместо `> file 2>&1`. -- Шаги 4 и 9 — без изменений (`-o` и `rm -f` работают). \ No newline at end of file +Реализовано: шаги 2, 3, 7 переписаны на встроенные инструменты (Write tool, stdout capture). +См. коммит с `fix(skills): убраны permission prompts из codex-review`. \ No newline at end of file diff --git a/agents/.agents/skills/codex-review/SKILL.md b/agents/.agents/skills/codex-review/SKILL.md index add8614..e6cd7e2 100644 --- a/agents/.agents/skills/codex-review/SKILL.md +++ b/agents/.agents/skills/codex-review/SKILL.md @@ -21,6 +21,8 @@ user_invocable: true ## Инструкции +> **Плейсхолдеры:** `${REVIEW_ID}` и `${CODEX_SESSION_ID}` в шагах ниже — это шаблонные плейсхолдеры, НЕ shell-переменные. Подставляй литеральные значения напрямую в каждый tool call. + ### Шаг 1: Определить режим ревью Определи, что ревьюить, по контексту и аргументам: @@ -41,39 +43,44 @@ user_invocable: true ### Шаг 2: Сгенерировать Session ID -```bash -REVIEW_ID="$(date +%s)-${RANDOM}" -``` +Сгенерируй уникальный `REVIEW_ID` самостоятельно, формат: `{unix_timestamp}-{случайное_4значное_число}`. +Пример: `1711872000-4821`. **НЕ используй bash** — подставляй значение напрямую в команды следующих шагов. ### Шаг 3: Подготовить материал для ревью **Ревью плана:** - Если план уже существует как файл (в `project/`, memory или где-то в репо) — использовать путь напрямую. НЕ копировать. -- Если план только в контексте разговора — записать в `/tmp/claude-plan-${REVIEW_ID}.md`. +- Если план только в контексте разговора — записать через **Write tool** в `/tmp/claude-plan-${REVIEW_ID}.md`. - **Обязательно вывести путь к файлу плана пользователю**, чтобы он мог открыть его в IDE: `📄 План для ревью: <путь-к-файлу>` **Ревью кода:** -Собрать diff в порядке приоритета (использовать первый непустой): +Собрать diff — выполняй по одной команде, используй первый непустой результат: -```bash -# 1. Unstaged changes -git diff > /tmp/claude-diff-${REVIEW_ID}.diff +1. `git diff` — unstaged changes +2. Если stdout пуст — `git diff --cached` — staged changes +3. Если пуст — `git diff main...HEAD` — все коммиты ветки -# 2. Если пусто — staged changes -if [ ! -s /tmp/claude-diff-${REVIEW_ID}.diff ]; then - git diff --cached > /tmp/claude-diff-${REVIEW_ID}.diff -fi +Каждую команду запускай отдельным Bash tool call (без перенаправлений). +Результат (stdout первой непустой команды) сохрани через **Write tool** в `/tmp/claude-diff-${REVIEW_ID}.diff`. -# 3. Если пусто — все коммиты ветки относительно main -if [ ! -s /tmp/claude-diff-${REVIEW_ID}.diff ]; then - git diff main...HEAD > /tmp/claude-diff-${REVIEW_ID}.diff -fi -``` +Если все три пусты — нет изменений для ревью, сообщи пользователю. -Если diff всё равно пуст — нет изменений для ревью, сообщить пользователю. +**Большие diff-ы (best-effort preflight):** перед захватом полного diff проверь размер +через `--shortstat` в том же порядке приоритета, что и основной diff: + +1. `git diff --shortstat` — если непусто, оценивай по нему +2. Если пусто — `git diff --cached --shortstat` +3. Если пусто — `git diff main...HEAD --shortstat` + +Вывод вида `3 files changed, 4200 insertions(+), 100 deletions(-)`. +Если insertions + deletions > 1500 — **останови сбор diff**, сообщи пользователю +что полный diff может быть обрезан Bash tool, и потребуй использовать `/codex-review <путь>` для конкретных файлов. +**НЕ продолжай** с потенциально неполным diff — частичное ревью хуже, чем отсутствие ревью. +Это best-effort проверка: `--shortstat` считает строки, а не байты. Diff с малым числом +очень длинных строк (минифицированный JSON, lockfile) может пройти порог, но быть огромным по размеру. **Ревью кода против плана:** подготовить и путь к плану, И diff. @@ -205,6 +212,8 @@ If changes are needed, end with exactly: VERDICT: REVISE" **Предпочтительно: `resume`** — экономит токены, сохраняет контекст сессии: +1. Запусти (без перенаправлений): + ```bash timeout 600 codex exec resume ${CODEX_SESSION_ID} \ "I've revised based on your feedback. @@ -212,10 +221,18 @@ timeout 600 codex exec resume ${CODEX_SESSION_ID} \ Here's what I changed: [Список правок] -Please re-review. End with VERDICT: APPROVED or VERDICT: REVISE" > /tmp/codex-review-${REVIEW_ID}.md 2>&1 +Please re-review. End with VERDICT: APPROVED or VERDICT: REVISE" ``` -**Примечание:** `resume` не поддерживает `-o`. Вывод перенаправляется в файл через `>`. Затем читай файл как обычно (Шаг 5). +Используй `timeout: 620000` в параметрах Bash tool. + +2. Bash tool захватывает и stdout, и stderr. Проверь результат по exit code: + - **exit 0** — успех. Сохрани stdout через **Write tool** в `/tmp/codex-review-${REVIEW_ID}.md`. + **Проверка полноты:** убедись что **последняя непустая строка** stdout — это ровно `VERDICT: APPROVED` или `VERDICT: REVISE`. Если вердикт отсутствует или не на последней строке — вывод мог быть обрезан; перейди к Fallback (свежий `codex exec -o`, который пишет напрямую в файл без риска truncation). + - **exit 124** — таймаут. Сообщи пользователю: "Codex не ответил за 10 минут" и предложи повторить (аналогично шагу 4). + - **другой exit code** — покажи пользователю stderr/stdout для диагностики, затем перейди к Fallback. + +**Примечание:** `resume` не поддерживает `-o`, поэтому вывод идёт в stdout Bash tool. Затем читай файл как обычно (Шаг 5). **Fallback** — если `resume` не сработал (сессия истекла, session ID не захвачен):