fix(tools): convert label names to int64 IDs for Forgejo create-issue #17
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/create-issue/labels-int64"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что сделано
Реализован lookup label names → int64 IDs в
.opencode/tools/_shared.tsдля Forgejo create-issue API.resolveLabelIds(full, names, opts):GET /repos/{repo}/labels→ строит mapname → id, для каждого имени из--labelsнаходит IDPOST /repos/{repo}/labelsс дефолтным цветом#ededed(agents могут использовать labels которых ещё нет на репо)issue createвcallForgejoGhтеперь отправляетlabels: [int64, ...](НЕ строки) вPOST /repos/{repo}/issueslabelsНЕ отправляется (раньше отправлялся пустой массив → 422 на некоторых версиях Forgejo)errorstring, issue НЕ создаётся (all-or-nothing)labels: string[]имён остаётся (обратная совместимость с GitHub-режимом и существующими вызовами)Тесты в
tests/test_create_issue_tool.py:test_labels_passed_to_gh— обновлён: проверяет GET /labels + POST /issues с int64 IDs[1, 2](вместо строк)test_labels_missing_autocreate— новый: несуществующий label → POST /labels (auto-create) + POST /issues с новым ID[42]test_labels_empty_omitted— новый: пустой labels → полеlabelsотсутствует в POST bodytest_labels_lookup_failure— новый: GET /labels падает → error surfaced, issue не созданПочему
Forgejo API
POST /api/v1/repos/{owner}/{repo}/issuesожидаетCreateIssueOption.labelsкак[]int64ID лейблов, НЕ строковых имён (в отличие от GitHub API который принимает строки). Tool отправлял["enhancement"]→ Forgejo возвращал HTTP 422 Unprocessable Entity. Контракты GitHub vs Forgejo расходятся — нужен внутренний lookup (issue #4).Решение по несуществующим labels: auto-create (а не error) — agents в автоматическом режиме не могут вручную создавать labels через UI, error блокировал бы pipeline. Auto-create с дефолтным цветом решает это; если и auto-create падает → возвращается понятная ошибка с именем label.
create-pr.tsНЕ использует labels (проверено) → баг #4 scope только в create-issue, отдельный issue не нужен.Watch out
_ts_loader.mjsне парсит TS-генерики в return-type inline-объектах (Promise<{...}>) иasassertions — пришлось вынестиLabelResult/Labelв type-aliases и убратьasassertion (runtimeJSON.parseвозвращаетanyв JS). При добавлении новых typed helpers в_shared.ts— использовать type-aliases, не inline-генерикиgit remote set-url origin https://slaid098:${FORGEJO_TOKEN}@...для push, затем возврат к чистому URL. НЕ коммитить URL с tokenpytest tests/test_create_issue_tool.py— pre-existing (TS не покрывается Python coverage). Полныйpytestнабор проходит (83.53%)check-permissions.py:114no-any-return — НЕ введён этим PR (на main, см. memory PR#14)ruff check/ruff format— все green, mypy greenPending
headfield) — НЕ пофиксен этим PR, остаётся открытым. Этот PR создан через raw curl workaround (как PR#12, PR#13)Closes #4
Code Review Summary
Реализация корректна:
resolveLabelIds()делает GET /labels → name→id map, автосоздаёт недостающие labels (POST /labels, color#ededed), передаётlabels: [int64, ...]в POST /issues, пустой массив → поле опускается. Внешний контракт tool'а (labels: string[]имён) сохранён — обратная совместимость не нарушена. All-or-nothing error handling: при провале lookup/auto-create issue не создаётся. 4 теста покрывают все сценарии (int64 IDs, autocreate, empty omitted, lookup failure). Полный набор зелёный: 785 passed, ruff clean для изменённых файлов.Positives
resolveLabelIds()— чистая, хорошо задокументированная функция (JSDoc объясняет «зачем»: Forgejo ожидает[]int64, не строки)resolved.error→return {status:1}до POST /issues (строки 218-220)create-issue.ts:24остаётсяlabels: string[], tool сам конвертирует — существующие вызовы не ломаются...(labelIds.length > 0 ? {labels: labelIds} : {}), строка 226) — исправляет 422 на некоторых версиях Forgejotest_labels_lookup_failureпроверяет, что issue НЕ создаётся при провале lookup (assert 1 fetch call, не 2)Suggestions (info, not blocking)
.opencode/skills/issue/SKILL.md:155-163 [docs] Документация устарела после этого PR. Строки 155-163 говорят: «Если label не существует в репо — tool упадёт. Создай через
curlк Forgejo API...». Теперь tool автосоздаёт labels — эта инструкция вводит агентов в заблуждение: они будут делать лишнийcurl POST /labelsперед вызовом tool (не ломает функциональность — label уже будет существовать в map — но лишняя работа). Рекомендуется обновить: заменить блок 155-163 на «Если label не существует — tool автосоздаст его с дефолтным цветом#ededed. Явное создание через curl нужно только если хочешь кастомный color».Fix: обновить
issue/SKILL.md:155-163в отдельном PR (cosmetic docs, не блокирует merge)..opencode/tools/_shared.ts:119 [robustness]
GET /repos/${full}/labelsбез?page=параметра возвращает только первую страницу (Forgejo default page size = 50). Если в репо >50 labels, label на странице 2+ не найдется в map → автосоздастся как дубликат (POST /labels создаст label с тем же name — Forgejo не запрещает дубликаты имён). Для репозиториев slaid098 (несколько labels) это unlikely, но для будущих масштабов стоит учесть.Fix (если понадобится): добавить пагинацию — цикл
?page=1&limit=50пока ответ не пустой, или?limit=10000(Forgejo поддерживает большой limit). Не блокирует merge — текущие репо <10 labels..opencode/tools/_shared.ts:127-146 [design] Auto-create без подтверждения: агент с опечаткой в label name (например
"bugg"вместо"bug") молча создаст мусорный label. Для single-user tool это приемлемо (YAGNI), но стоит задокументировать вissue/SKILL.mdкак known behavior. Не требует флага подтверждения — добавит сложность ради редкого кейса.Verdict: APPROVE