Add a close script to the sync layer #17

Closed
opened 2026-08-10 11:40:59 +00:00 by claude · 0 comments
Collaborator

Summary

Закрытие issue не покрыто ни одним скриптом. Единственный способ сменить
state — сырой tea api -X PATCH в обход слоя sync.

Spec

AGENTS.md

Motivation

AGENTS.md объявляет правилом: «Route all Gitea interaction through the tea
CLI via scripts». Закрытие — регулярная операция, и оно единственное из
регулярных, которого нет в skills/sync/scripts/. Там есть pull.py,
push.py, comment.py, remote.py, labels.py — и ничего для смены
состояния.

Наблюдалось при закрытии claude-skills/tea#11#15: пришлось пять раз
позвать

tea api --login "$GITEA_LOGIN" -X PATCH -d '{"state":"closed"}' \
    repos/claude-skills/tea/issues/15

Ломается сразу три вещи.

Правило слоёв. Сырой вызов знает и владельца, и репозиторий, и форму тела
запроса — всё то, что _gitea.py существует, чтобы прятать.

tea-runner. У субагента жёсткий запрет «Do not close, delete, or retitle
anything» (agents/tea-runner.md, правило 4). Он стоит там не потому, что
закрытие опасно по сути, а потому, что закрытие не покрыто скриптом: агенту
нечего вызвать, а сырой tea ему запрещён правилом 1. Появится close.py
запрет сузится до нормального «закрывай только то, что назвал вызывающий».

Разрешения. Чтобы пропустить сырой PATCH, оператор разрешает Bash(tea api *) — то есть весь API, включая -X DELETE по репозиторию. Узкий скрипт даёт
узкое разрешение.

Acceptance criteria

  • есть skills/sync/scripts/close.py, принимающий один или несколько id
  • за один прогон закрывает issue в Gitea и приводит state: в локальном
    файле в соответствие
  • --reopen выполняет обратную операцию
  • issue с origin: local закрыть нельзя: ошибка говорит, что issue не в
    трекере, и называет id
  • --dry-run печатает, что будет закрыто, и не делает ни одного пишущего
    запроса
  • локальный файл не меняется, если трекер ответил не 2xx
  • skills/sync/SKILL.md описывает команду
  • agents/tea-runner.md: правило 4 переписано — закрывать разрешено, но
    только те id, которые назвал вызывающий
  • есть тест с застабленным транспортом, включая путь «трекер ответил
    ошибкой → локальный файл не тронут»

Constraints

  • Не входит в объём: массовое закрытие по фильтру (--milestone, --label).
    Скрипт принимает явные id — закрытие набора issue это решение, а не пакетная
    операция.
  • Не входит в объём: удаление issue. Gitea умеет, но это не операция рабочего
    процесса.
  • Не входит в объём: закрытие с комментарием — для этого уже есть
    comment.py, вызывается отдельно.
<!-- tea:id close-issues-through-a-script --> ## Summary Закрытие issue не покрыто ни одним скриптом. Единственный способ сменить `state` — сырой `tea api -X PATCH` в обход слоя sync. ## Spec `AGENTS.md` ## Motivation `AGENTS.md` объявляет правилом: «Route all Gitea interaction through the `tea` CLI via scripts». Закрытие — регулярная операция, и оно единственное из регулярных, которого нет в `skills/sync/scripts/`. Там есть `pull.py`, `push.py`, `comment.py`, `remote.py`, `labels.py` — и ничего для смены состояния. Наблюдалось при закрытии `claude-skills/tea#11`–`#15`: пришлось пять раз позвать ```bash tea api --login "$GITEA_LOGIN" -X PATCH -d '{"state":"closed"}' \ repos/claude-skills/tea/issues/15 ``` Ломается сразу три вещи. **Правило слоёв.** Сырой вызов знает и владельца, и репозиторий, и форму тела запроса — всё то, что `_gitea.py` существует, чтобы прятать. **tea-runner.** У субагента жёсткий запрет «Do not close, delete, or retitle anything» (`agents/tea-runner.md`, правило 4). Он стоит там не потому, что закрытие опасно по сути, а потому, что закрытие не покрыто скриптом: агенту нечего вызвать, а сырой `tea` ему запрещён правилом 1. Появится `close.py` — запрет сузится до нормального «закрывай только то, что назвал вызывающий». **Разрешения.** Чтобы пропустить сырой PATCH, оператор разрешает `Bash(tea api *)` — то есть весь API, включая `-X DELETE` по репозиторию. Узкий скрипт даёт узкое разрешение. ## Acceptance criteria - [ ] есть `skills/sync/scripts/close.py`, принимающий один или несколько id - [ ] за один прогон закрывает issue в Gitea и приводит `state:` в локальном файле в соответствие - [ ] `--reopen` выполняет обратную операцию - [ ] issue с `origin: local` закрыть нельзя: ошибка говорит, что issue не в трекере, и называет id - [ ] `--dry-run` печатает, что будет закрыто, и не делает ни одного пишущего запроса - [ ] локальный файл не меняется, если трекер ответил не 2xx - [ ] `skills/sync/SKILL.md` описывает команду - [ ] `agents/tea-runner.md`: правило 4 переписано — закрывать разрешено, но только те id, которые назвал вызывающий - [ ] есть тест с застабленным транспортом, включая путь «трекер ответил ошибкой → локальный файл не тронут» ## Constraints - Не входит в объём: массовое закрытие по фильтру (`--milestone`, `--label`). Скрипт принимает явные id — закрытие набора issue это решение, а не пакетная операция. - Не входит в объём: удаление issue. Gitea умеет, но это не операция рабочего процесса. - Не входит в объём: закрытие с комментарием — для этого уже есть `comment.py`, вызывается отдельно.
claude added the comp/sync
type
task
labels 2026-08-10 11:40:59 +00:00
Sign in to join this conversation.