refactor(tools): cut GitHub fallback, Forgejo-first dispatch, fix 5 contradictions #5

Closed
opened 2026-08-07 09:45:08 +03:00 by slaid098 · 0 comments
Owner

Контекст

GitHub-аккаунт slaid098 удалён безвозвратно. Код opencode-config уже имеет Forgejo dispatch (_shared.ts:callForgejoGh, _forgejo_gh_dispatch в 3 oracle-скриптах), но GitHub-fallback ветки остаются как dead code в нашей среде. При этом 5 противоречий в поведении Forgejo vs GitHub приводят к некорректным результатам в Forgejo-режиме.

Forgejo-инстанс git.slaid098.dev версия 10.0.3. Actions API нестандартный: /actions/runs и /actions/runners возвращают 404. Workflow runs доступны только через /actions/tasks → {workflow_runs[], total_count}. Branch protection endpoint /branch_protections/{branch} поддерживается, но на main НЕ настроена (список пустой). README endpoint /readme НЕ работает (404) — нужен /contents/README.md.

Задача

  1. Вырезать GitHub-fallback ветки (5 мест):

    • _shared.ts:95 — spawnSync("gh", fullArgs) в fallback runGh() (срабатывает только если FORGEJO_URL не задан)
    • create-readme.ts:339-371 — GitHub API-ветка через spawnSync("gh", "api", ...) для GET/PUT README.md
    • pipeline-status.py:213-218 — fallback subprocess.run(args) для gh argv
    • spec-status.py:158-159 — fallback subprocess.run(args) для gh issue view
    • project-status.py:237-238 — fallback subprocess.run(args) для gh api
  2. Починить 5 критических противоречий:

    • CI rollup (pipeline-status.py:116-133, _forgejo_ci_rollup): использует commit status API /repos/{repo}/commits/{sha}/status — не видит Forgejo Actions runs. Заменить на /repos/{repo}/actions/tasks endpoint → {workflow_runs[], total_count} (поля: status, display_title, head_sha, created_at, url). Деталей jobs через API нет — это OK, возвращаем summary по runs.
    • Branch protection (project-status.py:1335-1342, _check_branch_protection): строковый match if "pull_request" in out and "required_status_checks" in out — этих полей НЕТ в Forgejo branch_protections response. Заменить на проверку Forgejo полей: enable_push (false = protected), enable_status_check, required_approvals. Возвращать OK если enable_push == false или required_approvals > 0 или enable_status_check == true.
    • Auth check (pipeline-status.py:193-194): check_gh_auth в Forgejo mode возвращает формальный (0, "", "") без проверки токена. Добавить реальную проверку: GET /api/v1/user с FORGEJO_TOKEN → если 200, auth OK; если 401/403, вернуть ошибку.
    • Pagination в gh pr list (pipeline-status.py:167-171): /repos/{repo}/pulls?state=open без пагинации → >50 PR теряются. Добавить ?page=N&limit=50 цикл пока ответ не пустой. Forgejo max_response_items: 50.
    • Hints ведут к ошибкам (pipeline-status.py:469, 633): gh pr checks и gh run view НЕ реализованы в Forgejo dispatch. Заменить hints на Forgejo-эквиваленты: ссылку на https://git.slaid098.dev/{repo}/actions (веб-UI), или удалить hints.
  3. Динамический clone URL (create-readme.ts:63): hardcoded https://github.com/slaid098/${repo_name}.git. Заменить на process.env.FORGEJO_URL ? ${process.env.FORGEJO_URL}/slaid098/${repo_name}.git : ... (fallback на GitHub-URL для обратной совместимости можно оставить в описании, но в коде — Forgejo-first).

  4. Расширить pipeline-status.py:_forgejo_gh_dispatch — добавить:

    • gh run list → GET /repos/{repo}/actions/tasks?limit=N → форматировать как databaseId,conclusion (маппинг id → databaseId, status/conclusion → conclusion)
    • (опционально) gh run view <id> → GET /repos/{repo}/actions/tasks + фильтр по id → вернуть display_title + logs URL (деталей jobs нет, это OK)
  5. Descriptions в tools (11 файлов): "GitHub issue" → "issue", "gh CLI" → "git-host CLI", "Actions API" → "CI", "GitHub Release" → "release". Конкретные file:line в разделе Влияние.

Контракты

  • Forgejo REST API: ${FORGEJO_URL}/api/v1/..., auth через Authorization: token ${FORGEJO_TOKEN}
  • /actions/tasks response: {workflow_runs: [{id, name, head_branch, head_sha, run_number, event, display_title, status, workflow_id, url, created_at, updated_at}], total_count}
  • /branch_protections/{branch} response: {branch_name, enable_push, enable_push_whitelist, enable_push_whitelist_teams, enable_status_check, status_check_contexts, required_approvals, enable_merge_whitelist, ...}
  • /user response: {id, login, is_admin, ...} — для auth check
  • /repos/{repo}/pulls?page=N&limit=50 — пагинация, 50 max per page
  • TS fetch() для HTTP (уже используется в _shared.ts:66)
  • Python urllib.request.urlopen() для HTTP (уже используется в pipeline-status.py:104)
  • process.env.FORGEJO_URL и process.env.FORGEJO_TOKEN — env vars (уже читаются)

Инварианты

  • Внешний контракт tools НЕ меняется — create-issue, create-pr, merge-pr, post-review, pipeline-status, project-status, spec-status остаются с теми же параметрами
  • gh CLI больше НЕ используется в коде (только fetch/urllib/curl к Forgejo API)
  • GitHub-fallback вырезается ПОЛНОСТЬЮ (если FORGEJO_URL не задан — ошибка, не fallback на gh)
  • Новые tools НЕ создаются (это следующий issue, не этот)
  • .github/workflows/ НЕ трогаем (рабочий CI, Forgejo понимает оба расположения)
  • Описания tools: host-agnostic ("issue", не "GitHub issue"/"Forgejo issue") — задел на будущие зеркала

Граничные случаи

  • FORGEJO_URL не задан → tool должен вернуть понятную ошибку "FORGEJO_URL env var required", НЕ fallback на gh
  • /actions/tasks пустой ответ (нет runs) → CI check возвращает "нет CI runs" (не ошибка)
  • /branch_protections/main 404 → branch protection check возвращает WARN "не настроена" (реальное состояние, не баг)
  • /user 401/403 → auth check возвращает FAIL с сообщением "FORGEJO_TOKEN невалиден"
  • Pagination: если total PR < 50, цикл из одной итерации; если ровно 50 — продолжать на page=2
  • /actions/tasks total_count > 50 — пагинация НЕ поддерживается этим endpoint (по разведке), возвращаем первые 50 + hint "больше 50 runs, см. веб-UI"
  • create-readme.ts:63 — если FORGEJO_URL не задан (теоретический случай после вырезания fallback), вернуть ошибку

Влияние на связанные компоненты

  • _shared.ts — основной файл правки (вырезать L94-95 fallback, обновить комментарии L11/25/86/100/181 с "GitHub tools" → "git-host tools")
  • create-readme.ts — вырезать L339-371 GitHub-ветку, оставить L300-338 Forgejo-ветку; L63 динамический clone URL; description L135/L218/L370 убрать "GitHub API"
  • pipeline-status.py — починить L116-133 (CI rollup), L167-171 (pagination), L193-194 (auth), L213-218 (fallback), L469/L633 (hints); добавить _forgejo_gh_dispatch для gh run list; docstrings L4/L14/L287/L294/L326/L476 убрать "GitHub"
  • pipeline-status.ts:6-8 — description "Actions API" → "CI"
  • spec-status.py — вырезать L158-159 fallback; docstrings L9/L34/L166/L173 убрать "GitHub"; L435 NEXT_ACTION "GitHub issues" → "issues"
  • project-status.py — починить L1335-1342 (branch protection); вырезать L237-238 fallback; docstrings L13/L218/L1241/L1286/L1334/L1338 убрать "GitHub"
  • project-status.ts:7,10 — description "dependabot"/"gh" → host-agnostic; аргумент "branch protection via gh" → "branch protection via API"
  • create-issue.ts:20 — description "GitHub issue" → "issue"
  • create-pr.ts:19 — description "GitHub PR" → "PR"
  • merge-pr.ts:5,8 — description "gh pr merge" → "merge PR"
  • post-review.ts:8 — description "gh pr comment" → "post review comment"
  • spec-status.ts — обёртка, без изменений (dispatch в Python)
  • Issue #4 (bug create-issue labels) — НЕ зависит от этого PR (может быть закрыт независимо)
  • Issue #1 (port to Forgejo) — устарел, может быть закрыт (портирование фактически завершено, этот PR вырезает остатки)

Вне scope

  • Новые tools (release-create, repo-edit, repo-create, label-create) — PR 2 (skills используют прямой curl)
  • Skills/agents правки — отдельный PR 2
  • Dockerfile/opencode.json/.env.example — отдельный PR 3
  • Templates (cookiecutter) — отдельный PR 4
  • .github/workflows/ → .forgejo/workflows/ — НЕ делаем (рабочий CI, Forgejo понимает оба)
  • Настройка act_runner (инфра, отдельная задача)
  • Зеркала на GitLab/Codeberg (отдельная задача)
  • Витрина slaid098-dev (отдельный репо)
  • Политика приватности репозиториев (отдельная задача)

Критерии приемки

  • rg "spawnSync.*gh" .opencode/tools/ → 0 совпадений (все gh spawnSync вырезаны)
  • rg "subprocess.run.*gh" .opencode/scripts/ → 0 совпадений (все gh subprocess вырезаны, кроме dispatch который перехватывает)
  • rg "github.com" .opencode/tools/ .opencode/scripts/ → 0 совпадений в активном коде (только в docstring-примерах можно оставить как generic URL)
  • pipeline-status({pr_number: N}) в Forgejo-режиме возвращает CI status из /actions/tasks, не из commit status API
  • project-status в Forgejo-режиме проверяет branch protection по Forgejo полям (enable_push, required_approvals), не по GitHub Rules API (pull_request, required_status_checks)
  • pipeline-status auth check в Forgejo-режиме делает реальный GET /user запрос, не формальный OK
  • pipeline-status в репо с >50 PR не теряет PR (пагинация работает)
  • create-readme.ts clone URL в генерируемом README — ${FORGEJO_URL}/slaid098/{repo}.git, не github.com/slaid098/{repo}.git
  • Все tool descriptions: "GitHub" → host-agnostic (rg "GitHub" в descriptions → 0)
  • FORGEJO_URL не задан → tools возвращают понятную ошибку, НЕ fallback на gh
  • Smoke-test: pipeline-status({pr_number: N}) на реальном PR возвращает статус (DONE/NOT_DONE/AMBIGUOUS), не падает
## Контекст GitHub-аккаунт `slaid098` удалён безвозвратно. Код opencode-config уже имеет Forgejo dispatch (`_shared.ts:callForgejoGh`, `_forgejo_gh_dispatch` в 3 oracle-скриптах), но GitHub-fallback ветки остаются как dead code в нашей среде. При этом 5 противоречий в поведении Forgejo vs GitHub приводят к некорректным результатам в Forgejo-режиме. Forgejo-инстанс `git.slaid098.dev` версия 10.0.3. Actions API нестандартный: `/actions/runs` и `/actions/runners` возвращают 404. Workflow runs доступны только через `/actions/tasks` → `{workflow_runs[], total_count}`. Branch protection endpoint `/branch_protections/{branch}` поддерживается, но на main НЕ настроена (список пустой). README endpoint `/readme` НЕ работает (404) — нужен `/contents/README.md`. ## Задача 1. **Вырезать GitHub-fallback ветки** (5 мест): - `_shared.ts:95` — `spawnSync("gh", fullArgs)` в fallback `runGh()` (срабатывает только если `FORGEJO_URL` не задан) - `create-readme.ts:339-371` — GitHub API-ветка через `spawnSync("gh", "api", ...)` для GET/PUT README.md - `pipeline-status.py:213-218` — fallback `subprocess.run(args)` для `gh` argv - `spec-status.py:158-159` — fallback `subprocess.run(args)` для `gh issue view` - `project-status.py:237-238` — fallback `subprocess.run(args)` для `gh api` 2. **Починить 5 критических противоречий:** - **CI rollup** (`pipeline-status.py:116-133`, `_forgejo_ci_rollup`): использует commit status API `/repos/{repo}/commits/{sha}/status` — не видит Forgejo Actions runs. Заменить на `/repos/{repo}/actions/tasks` endpoint → `{workflow_runs[], total_count}` (поля: `status`, `display_title`, `head_sha`, `created_at`, `url`). Деталей jobs через API нет — это OK, возвращаем summary по runs. - **Branch protection** (`project-status.py:1335-1342`, `_check_branch_protection`): строковый match `if "pull_request" in out and "required_status_checks" in out` — этих полей НЕТ в Forgejo `branch_protections` response. Заменить на проверку Forgejo полей: `enable_push` (false = protected), `enable_status_check`, `required_approvals`. Возвращать OK если `enable_push == false` или `required_approvals > 0` или `enable_status_check == true`. - **Auth check** (`pipeline-status.py:193-194`): `check_gh_auth` в Forgejo mode возвращает формальный `(0, "", "")` без проверки токена. Добавить реальную проверку: `GET /api/v1/user` с `FORGEJO_TOKEN` → если 200, auth OK; если 401/403, вернуть ошибку. - **Pagination в `gh pr list`** (`pipeline-status.py:167-171`): `/repos/{repo}/pulls?state=open` без пагинации → >50 PR теряются. Добавить `?page=N&limit=50` цикл пока ответ не пустой. Forgejo `max_response_items: 50`. - **Hints ведут к ошибкам** (`pipeline-status.py:469, 633`): `gh pr checks` и `gh run view` НЕ реализованы в Forgejo dispatch. Заменить hints на Forgejo-эквиваленты: ссылку на `https://git.slaid098.dev/{repo}/actions` (веб-UI), или удалить hints. 3. **Динамический clone URL** (`create-readme.ts:63`): hardcoded `https://github.com/slaid098/${repo_name}.git`. Заменить на `process.env.FORGEJO_URL ? `${process.env.FORGEJO_URL}/slaid098/${repo_name}.git` : ...` (fallback на GitHub-URL для обратной совместимости можно оставить в описании, но в коде — Forgejo-first). 4. **Расширить `pipeline-status.py:_forgejo_gh_dispatch`** — добавить: - `gh run list` → `GET /repos/{repo}/actions/tasks?limit=N` → форматировать как `databaseId,conclusion` (маппинг `id` → databaseId, `status`/`conclusion` → conclusion) - (опционально) `gh run view <id>` → `GET /repos/{repo}/actions/tasks` + фильтр по id → вернуть `display_title` + logs URL (деталей jobs нет, это OK) 5. **Descriptions в tools** (11 файлов): "GitHub issue" → "issue", "gh CLI" → "git-host CLI", "Actions API" → "CI", "GitHub Release" → "release". Конкретные file:line в разделе Влияние. ## Контракты - Forgejo REST API: `${FORGEJO_URL}/api/v1/...`, auth через `Authorization: token ${FORGEJO_TOKEN}` - `/actions/tasks` response: `{workflow_runs: [{id, name, head_branch, head_sha, run_number, event, display_title, status, workflow_id, url, created_at, updated_at}], total_count}` - `/branch_protections/{branch}` response: `{branch_name, enable_push, enable_push_whitelist, enable_push_whitelist_teams, enable_status_check, status_check_contexts, required_approvals, enable_merge_whitelist, ...}` - `/user` response: `{id, login, is_admin, ...}` — для auth check - `/repos/{repo}/pulls?page=N&limit=50` — пагинация, 50 max per page - TS `fetch()` для HTTP (уже используется в `_shared.ts:66`) - Python `urllib.request.urlopen()` для HTTP (уже используется в `pipeline-status.py:104`) - `process.env.FORGEJO_URL` и `process.env.FORGEJO_TOKEN` — env vars (уже читаются) ## Инварианты - Внешний контракт tools НЕ меняется — `create-issue`, `create-pr`, `merge-pr`, `post-review`, `pipeline-status`, `project-status`, `spec-status` остаются с теми же параметрами - `gh` CLI больше НЕ используется в коде (только `fetch`/`urllib`/`curl` к Forgejo API) - GitHub-fallback вырезается ПОЛНОСТЬЮ (если `FORGEJO_URL` не задан — ошибка, не fallback на `gh`) - Новые tools НЕ создаются (это следующий issue, не этот) - `.github/workflows/` НЕ трогаем (рабочий CI, Forgejo понимает оба расположения) - Описания tools: host-agnostic ("issue", не "GitHub issue"/"Forgejo issue") — задел на будущие зеркала ## Граничные случаи - `FORGEJO_URL` не задан → tool должен вернуть понятную ошибку "FORGEJO_URL env var required", НЕ fallback на `gh` - `/actions/tasks` пустой ответ (нет runs) → CI check возвращает "нет CI runs" (не ошибка) - `/branch_protections/main` 404 → branch protection check возвращает WARN "не настроена" (реальное состояние, не баг) - `/user` 401/403 → auth check возвращает FAIL с сообщением "FORGEJO_TOKEN невалиден" - Pagination: если total PR < 50, цикл из одной итерации; если ровно 50 — продолжать на page=2 - `/actions/tasks` total_count > 50 — пагинация НЕ поддерживается этим endpoint (по разведке), возвращаем первые 50 + hint "больше 50 runs, см. веб-UI" - `create-readme.ts:63` — если `FORGEJO_URL` не задан (теоретический случай после вырезания fallback), вернуть ошибку ## Влияние на связанные компоненты - `_shared.ts` — основной файл правки (вырезать L94-95 fallback, обновить комментарии L11/25/86/100/181 с "GitHub tools" → "git-host tools") - `create-readme.ts` — вырезать L339-371 GitHub-ветку, оставить L300-338 Forgejo-ветку; L63 динамический clone URL; description L135/L218/L370 убрать "GitHub API" - `pipeline-status.py` — починить L116-133 (CI rollup), L167-171 (pagination), L193-194 (auth), L213-218 (fallback), L469/L633 (hints); добавить `_forgejo_gh_dispatch` для `gh run list`; docstrings L4/L14/L287/L294/L326/L476 убрать "GitHub" - `pipeline-status.ts:6-8` — description "Actions API" → "CI" - `spec-status.py` — вырезать L158-159 fallback; docstrings L9/L34/L166/L173 убрать "GitHub"; L435 NEXT_ACTION "GitHub issues" → "issues" - `project-status.py` — починить L1335-1342 (branch protection); вырезать L237-238 fallback; docstrings L13/L218/L1241/L1286/L1334/L1338 убрать "GitHub" - `project-status.ts:7,10` — description "dependabot"/"gh" → host-agnostic; аргумент "branch protection via gh" → "branch protection via API" - `create-issue.ts:20` — description "GitHub issue" → "issue" - `create-pr.ts:19` — description "GitHub PR" → "PR" - `merge-pr.ts:5,8` — description "gh pr merge" → "merge PR" - `post-review.ts:8` — description "gh pr comment" → "post review comment" - `spec-status.ts` — обёртка, без изменений (dispatch в Python) - Issue #4 (bug create-issue labels) — НЕ зависит от этого PR (может быть закрыт независимо) - Issue #1 (port to Forgejo) — устарел, может быть закрыт (портирование фактически завершено, этот PR вырезает остатки) ## Вне scope - Новые tools (release-create, repo-edit, repo-create, label-create) — PR 2 (skills используют прямой curl) - Skills/agents правки — отдельный PR 2 - Dockerfile/opencode.json/.env.example — отдельный PR 3 - Templates (cookiecutter) — отдельный PR 4 - `.github/workflows/` → `.forgejo/workflows/` — НЕ делаем (рабочий CI, Forgejo понимает оба) - Настройка act_runner (инфра, отдельная задача) - Зеркала на GitLab/Codeberg (отдельная задача) - Витрина slaid098-dev (отдельный репо) - Политика приватности репозиториев (отдельная задача) ## Критерии приемки - [ ] `rg "spawnSync.*gh" .opencode/tools/` → 0 совпадений (все `gh` spawnSync вырезаны) - [ ] `rg "subprocess.run.*gh" .opencode/scripts/` → 0 совпадений (все `gh` subprocess вырезаны, кроме dispatch который перехватывает) - [ ] `rg "github.com" .opencode/tools/ .opencode/scripts/` → 0 совпадений в активном коде (только в docstring-примерах можно оставить как generic URL) - [ ] `pipeline-status({pr_number: N})` в Forgejo-режиме возвращает CI status из `/actions/tasks`, не из commit status API - [ ] `project-status` в Forgejo-режиме проверяет branch protection по Forgejo полям (`enable_push`, `required_approvals`), не по GitHub Rules API (`pull_request`, `required_status_checks`) - [ ] `pipeline-status` auth check в Forgejo-режиме делает реальный `GET /user` запрос, не формальный OK - [ ] `pipeline-status` в репо с >50 PR не теряет PR (пагинация работает) - [ ] `create-readme.ts` clone URL в генерируемом README — `${FORGEJO_URL}/slaid098/{repo}.git`, не `github.com/slaid098/{repo}.git` - [ ] Все tool descriptions: "GitHub" → host-agnostic (rg "GitHub" в descriptions → 0) - [ ] `FORGEJO_URL` не задан → tools возвращают понятную ошибку, НЕ fallback на `gh` - [ ] Smoke-test: `pipeline-status({pr_number: N})` на реальном PR возвращает статус (DONE/NOT_DONE/AMBIGUOUS), не падает
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/opencode-config#5
No description provided.