feat(release): global create_release.py with deterministic OS marking #65

Closed
opened 2026-08-12 18:35:41 +03:00 by slaid098 · 0 comments
Owner

Контекст

Сейчас create_release.py живёт в каждом репо отдельно (voice_assistant, video_uniq) — риск рассинхрона, нет единого стандарта. Платформа в release notes не маркируется детерминированно: asset filename voice-assistant.zip без ОС, в body нет секции "Системные требования". Пользователь на странице релиза не видит для какой ОС сборка.

Исследование 4 топ-проектов (VSCode/OBS/yt-dlp/espanso) показало стандарт: asset filename содержит ОС (OBS-Studio-32.2.1-Windows-x64-Installer.exe, yt-dlp.exe, Espanso-Mac-Universal.zip), а min версия ОС упоминается в important notes (когда поднимается).

Issue #63 (closed) удалил platform arg из create-changelog tool — платформа не должна быть в CHANGELOG (не детерминировано, пишет агент). Платформа должна быть в CI (детерминированно).

Задача

  1. Создать .opencode/scripts/create_release.py — глобальная утилита, self-contained (только stdlib: re, pathlib, tomllib, json, os, urllib.request):

    • Читает pyproject.toml → поле name (через tomllib, Python 3.11+)
    • Парсит ОС из RUNNER_OS env var (Forgejo/act runner auto-set):
      • Windows + RUNNER_ARCH=X64 → windows-x64
      • Linux + X64 → linux-x64
      • macOS + ARM64 → macos-arm64
    • Формирует asset filename: {name}-{os}-{arch}.zip (например voice-assistant-windows-x64.zip)
    • Читает RELEASE_PLATFORM env var (из release.yml) — полная строка для body (например "Windows 10+ (64-bit)")
    • Читает CHANGELOG.md через встроенный экстрактор (копия логики release_notes.py из voice_assistant):
      • extract_changelog_section(Path("CHANGELOG.md"), tag) — regex-поиск секции по тегу, срез заголовка/шапки/links, fallback на весь CHANGELOG если секция не найдена
    • Формирует body релиза:
      if release_platform:
          platform_section = f"## Системные требования\n\n**ОС:** {release_platform}\n\n---\n\n"
          body = platform_section + changelog_section
      else:
          body = changelog_section
      
    • Создаёт релиз на Forgejo (idempotent: delete existing release для тега → create new)
    • Загружает asset {name}-{os}-{arch}.zip через multipart/form-data (urllib)
    • Использует env vars: FORGEJO_URL (или GITHUB_SERVER_URL), GITHUB_TOKEN, GITHUB_REF_NAME (тег), RELEASE_PLATFORM, RUNNER_OS, RUNNER_ARCH
  2. Создать .opencode/scripts/release_notes.py — экстрактор секции CHANGELOG (self-contained, stdlib only):

    • extract_changelog_section(changelog_path: Path, tag: str) -> str
    • Логика идентична voice_assistant scripts/release_notes.py (PR #27): regex ^## [VERSION] [-—] YYYY-MM-DD, срез заголовка/шапки/trailing links, 3 fallback'а
    • Используется create_release.py через импорт
  3. Обновить skills/release/SKILL.md — новый раздел «Платформа в релизе (детерминированный стандарт)»:

    • Asset filename: {name}-{os}-{arch}.zip — формируется из pyproject.toml name + RUNNER_OS/RUNNER_ARCH
    • Body: секция "Системные требования" из RELEASE_PLATFORM env var в release.yml (препендится к CHANGELOG-секции)
    • Источник правды: pyproject.toml (name) + runner (OS) + release.yml env var (platform display)
    • Утилита: create_release.py скачивается из opencode-config через curl в каждом репо (одна версия, не хранится локально)
    • Пример release.yml (шаблон в SKILL.md):
      - name: Create Forgejo release
        env:
          RELEASE_PLATFORM: "Windows 10+ (64-bit)"
        run: |
          curl -fsSL "$FORGEJO_URL/slaid098/opencode-config/raw/branch/main/.opencode/scripts/create_release.py" \
            -o scripts/create_release.py
          python scripts/create_release.py
      
    • Min версия ОС поднимается → обновить RELEASE_PLATFORM env var в release.yml + запись в CHANGELOG ### Изменено текстом: «Минимальная версия Windows повышена до 11» (как yt-dlp important changes)
    • Multi-platform (будущее): отдельный job в release.yml на каждую ОС, каждый со своим RELEASE_PLATFORM и asset filename
    • Чего НЕ делать: НЕ **Платформы:** заголовок (нарушает стандарт, удалён issue #63), НЕ Python в требованиях (встроен в exe через PyInstaller), НЕ писать платформу в CHANGELOG (не детерминировано, агент может забыть)
  4. Удалить старое упоминание platform arg из SKILL.md (если осталось после issue #63)

Контракты

Что создаётся

  • .opencode/scripts/create_release.py — глобальная утилита, self-contained (stdlib only)
  • .opencode/scripts/release_notes.py — экстрактор, self-contained (stdlib only)

Что обновляется

  • skills/release/SKILL.md — раздел платформы + пример release.yml

Что НЕ меняется

  • create-changelog tool — уже без platform arg (issue #63 closed)
  • skills/repo-readme/SKILL.md — НЕ трогать
  • Другие skills — НЕ трогать

Формат asset filename

{name}-{os}-{arch}.zip где:

  • name = pyproject.toml field name (например voice-assistant)
  • os = lowercase RUNNER_OS (windows/linux/macos)
  • arch = lowercase RUNNER_ARCH (x64/arm64)
  • Пример: voice-assistant-windows-x64.zip

Формат body релиза

## Системные требования

**ОС:** {RELEASE_PLATFORM}

---

{changelog_section}

Где changelog_section = результат extract_changelog_section(CHANGELOG.md, tag) — bullets секции текущей версии (без шапки, без links).

Если RELEASE_PLATFORM не задан → body = changelog_section (backward compat, без секции).

Что НЕ входит в body

  • Шапка CHANGELOG (# Changelog, «Все заметные изменения...», «Формат: ...») — срезается экстрактором
  • Self-referential links ([0.1.0]: https://...) — срезаются экстрактором
  • Python version (встроен в exe)
  • **Платформы:** заголовок (удалён issue #63)

Инварианты

  • create_release.py self-contained: только stdlib (re, pathlib, tomllib, json, os, urllib.request), без внешних зависимостей
  • release_notes.py self-contained: только stdlib
  • Утилита детерминированная: одинаковый pyproject.toml + runner env + CHANGELOG + tag → одинаковый asset filename и body
  • Идемпотентность: delete existing release для тега → create new (как в voice_assistant PR #18)
  • Fallback: RELEASE_PLATFORM не задан → body без секции (backward compat)
  • Fallback: секция CHANGELOG не найдена → весь CHANGELOG + warning (как в voice_assistant PR #27)
  • Fallback: CHANGELOG.md отсутствует → body = "" (релиз без нот, не падать)
  • Fallback: pyproject.toml отсутствует → ошибка (имя проекта обязательно)
  • Fallback: RUNNER_OS не задан → ошибка (ОС обязательна для asset filename)

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

  • pyproject.toml без field name → ошибка с сообщением "field 'name' not found in pyproject.toml"
  • RUNNER_OS=Windows + RUNNER_ARCH=X64 → windows-x64 (lowercase)
  • RUNNER_OS=Linux + RUNNER_ARCH=X64 → linux-x64
  • RUNNER_OS=macOS + RUNNER_ARCH=ARM64 → macos-arm64 (lowercase os, но macOS → macos)
  • RUNNER_OS=macOS + RUNNER_ARCH=X64 → macos-x64 (Intel Mac)
  • RELEASE_PLATFORM="" (пустая строка) → body без секции (backward compat, как omitted)
  • RELEASE_PLATFORM="Windows 10+ (64-bit)" → body с секцией
  • Forgejo API: release для тега уже существует → delete then create (idempotent)
  • Forgejo API: attachment upload fails → релиз создан, но ассет не загружен → залогировать ошибку, не падать (релиз без ассета лучше чем нет релиза)
  • CHANGELOG с тире — вместо дефиса - в заголовке (## [0.1.0] — 2026-08-11) → regex покрывает оба
  • Asset filename с дефисом в имени проекта: voice-assistant → voice-assistant-windows-x64.zip (не двойной дефис)

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

  • skills/release/SKILL.md — основной файл обновления
  • .opencode/scripts/create_release.py — новый файл
  • .opencode/scripts/release_notes.py — новый файл
  • 下游 репо (voice_assistant, video_uniq) — отдельные issues для миграции на глобальный create_release.py:
    • voice_assistant: issue (отдельный) — удалить локальный scripts/create_release.py, скачать глобальный через curl в release.yml, добавить RELEASE_PLATFORM env var
    • video_uniq: issue (отдельный, будущее) — аналогично
  • create-changelog tool — НЕ трогать (issue #63 closed, platform arg уже удалён)
  • skills/repo-readme/SKILL.md — НЕ трогать

Вне scope

  • Миграция voice_assistant на глобальный create_release.py — отдельный issue в voice_assistant
  • Миграция video_uniq — отдельный issue в video_uniq (будущее)
  • Reusable workflow / composite action — НЕ в этом issue (выбран download через curl, проще)
  • Multi-platform support (Linux/macOS jobs) — будущее, стандарт описан в SKILL.md но реализация в каждом репо отдельно
  • PATCH существующего релиза v0.1.0 в voice_assistant — отдельная операция после merge миграции voice_assistant
  • release_notes.py тесты — опционально, если opencode-config имеет тест-инфраструктуру для Python скриптов

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

  • .opencode/scripts/create_release.py создан — self-contained (stdlib only), читает pyproject.toml name, парсит RUNNER_OS/ARCH, формирует asset filename {name}-{os}-{arch}.zip, формирует body с секцией "Системные требования" из RELEASE_PLATFORM, idempotent (delete+create)
  • .opencode/scripts/release_notes.py создан — self-contained (stdlib only), extract_changelog_section(path, tag), 3 fallback'а (секция не найдена, CHANGELOG отсутствует, пустая секция)
  • skills/release/SKILL.md — раздел «Платформа в релизе (детерминированный стандарт)» добавлен
  • SKILL.md — пример release.yml с curl-download create_release.py + RELEASE_PLATFORM env var
  • SKILL.md — описан формат asset filename {name}-{os}-{arch}.zip
  • SKILL.md — описан формат body (секция "Системные требования" + bullets)
  • SKILL.md — описаны правила: min ОС поднимается → CHANGELOG ### Изменено + обновить RELEASE_PLATFORM; multi-platform → отдельный job на ОС
  • SKILL.md — описаны anti-patterns: НЕ **Платформы:** заголовок, НЕ Python в требованиях, НЕ платформа в CHANGELOG
  • Старое упоминание platform arg удалено из SKILL.md (если осталось после issue #63)
## Контекст Сейчас `create_release.py` живёт в каждом репо отдельно (voice_assistant, video_uniq) — риск рассинхрона, нет единого стандарта. Платформа в release notes не маркируется детерминированно: asset filename `voice-assistant.zip` без ОС, в body нет секции "Системные требования". Пользователь на странице релиза не видит для какой ОС сборка. Исследование 4 топ-проектов (VSCode/OBS/yt-dlp/espanso) показало стандарт: asset filename содержит ОС (`OBS-Studio-32.2.1-Windows-x64-Installer.exe`, `yt-dlp.exe`, `Espanso-Mac-Universal.zip`), а min версия ОС упоминается в important notes (когда поднимается). Issue #63 (closed) удалил `platform` arg из `create-changelog` tool — платформа не должна быть в CHANGELOG (не детерминировано, пишет агент). Платформа должна быть в CI (детерминированно). ## Задача 1. Создать `.opencode/scripts/create_release.py` — глобальная утилита, self-contained (только stdlib: re, pathlib, tomllib, json, os, urllib.request): - Читает `pyproject.toml` → поле `name` (через `tomllib`, Python 3.11+) - Парсит ОС из `RUNNER_OS` env var (Forgejo/act runner auto-set): - `Windows` + `RUNNER_ARCH=X64` → `windows-x64` - `Linux` + `X64` → `linux-x64` - `macOS` + `ARM64` → `macos-arm64` - Формирует asset filename: `{name}-{os}-{arch}.zip` (например `voice-assistant-windows-x64.zip`) - Читает `RELEASE_PLATFORM` env var (из release.yml) — полная строка для body (например "Windows 10+ (64-bit)") - Читает `CHANGELOG.md` через встроенный экстрактор (копия логики release_notes.py из voice_assistant): - `extract_changelog_section(Path("CHANGELOG.md"), tag)` — regex-поиск секции по тегу, срез заголовка/шапки/links, fallback на весь CHANGELOG если секция не найдена - Формирует body релиза: ```python if release_platform: platform_section = f"## Системные требования\n\n**ОС:** {release_platform}\n\n---\n\n" body = platform_section + changelog_section else: body = changelog_section ``` - Создаёт релиз на Forgejo (idempotent: delete existing release для тега → create new) - Загружает asset `{name}-{os}-{arch}.zip` через multipart/form-data (urllib) - Использует env vars: `FORGEJO_URL` (или `GITHUB_SERVER_URL`), `GITHUB_TOKEN`, `GITHUB_REF_NAME` (тег), `RELEASE_PLATFORM`, `RUNNER_OS`, `RUNNER_ARCH` 2. Создать `.opencode/scripts/release_notes.py` — экстрактор секции CHANGELOG (self-contained, stdlib only): - `extract_changelog_section(changelog_path: Path, tag: str) -> str` - Логика идентична voice_assistant `scripts/release_notes.py` (PR #27): regex `^## [VERSION] [-—] YYYY-MM-DD`, срез заголовка/шапки/trailing links, 3 fallback'а - Используется `create_release.py` через импорт 3. Обновить `skills/release/SKILL.md` — новый раздел **«Платформа в релизе (детерминированный стандарт)»**: - **Asset filename**: `{name}-{os}-{arch}.zip` — формируется из pyproject.toml `name` + `RUNNER_OS`/`RUNNER_ARCH` - **Body**: секция "Системные требования" из `RELEASE_PLATFORM` env var в release.yml (препендится к CHANGELOG-секции) - **Источник правды**: pyproject.toml (name) + runner (OS) + release.yml env var (platform display) - **Утилита**: `create_release.py` скачивается из opencode-config через curl в каждом репо (одна версия, не хранится локально) - **Пример release.yml** (шаблон в SKILL.md): ```yaml - name: Create Forgejo release env: RELEASE_PLATFORM: "Windows 10+ (64-bit)" run: | curl -fsSL "$FORGEJO_URL/slaid098/opencode-config/raw/branch/main/.opencode/scripts/create_release.py" \ -o scripts/create_release.py python scripts/create_release.py ``` - **Min версия ОС поднимается** → обновить `RELEASE_PLATFORM` env var в release.yml + запись в CHANGELOG `### Изменено` текстом: «Минимальная версия Windows повышена до 11» (как yt-dlp important changes) - **Multi-platform (будущее)**: отдельный job в release.yml на каждую ОС, каждый со своим `RELEASE_PLATFORM` и asset filename - **Чего НЕ делать**: НЕ `**Платформы:**` заголовок (нарушает стандарт, удалён issue #63), НЕ Python в требованиях (встроен в exe через PyInstaller), НЕ писать платформу в CHANGELOG (не детерминировано, агент может забыть) 4. Удалить старое упоминание `platform` arg из SKILL.md (если осталось после issue #63) ## Контракты ### Что создаётся - `.opencode/scripts/create_release.py` — глобальная утилита, self-contained (stdlib only) - `.opencode/scripts/release_notes.py` — экстрактор, self-contained (stdlib only) ### Что обновляется - `skills/release/SKILL.md` — раздел платформы + пример release.yml ### Что НЕ меняется - `create-changelog` tool — уже без `platform` arg (issue #63 closed) - `skills/repo-readme/SKILL.md` — НЕ трогать - Другие skills — НЕ трогать ### Формат asset filename `{name}-{os}-{arch}.zip` где: - `name` = `pyproject.toml` field `name` (например `voice-assistant`) - `os` = lowercase `RUNNER_OS` (windows/linux/macos) - `arch` = lowercase `RUNNER_ARCH` (x64/arm64) - Пример: `voice-assistant-windows-x64.zip` ### Формат body релиза ``` ## Системные требования **ОС:** {RELEASE_PLATFORM} --- {changelog_section} ``` Где `changelog_section` = результат `extract_changelog_section(CHANGELOG.md, tag)` — bullets секции текущей версии (без шапки, без links). Если `RELEASE_PLATFORM` не задан → body = `changelog_section` (backward compat, без секции). ### Что НЕ входит в body - Шапка CHANGELOG (`# Changelog`, «Все заметные изменения...», «Формат: ...») — срезается экстрактором - Self-referential links (`[0.1.0]: https://...`) — срезаются экстрактором - Python version (встроен в exe) - `**Платформы:**` заголовок (удалён issue #63) ## Инварианты - `create_release.py` self-contained: только stdlib (re, pathlib, tomllib, json, os, urllib.request), без внешних зависимостей - `release_notes.py` self-contained: только stdlib - Утилита детерминированная: одинаковый pyproject.toml + runner env + CHANGELOG + tag → одинаковый asset filename и body - Идемпотентность: delete existing release для тега → create new (как в voice_assistant PR #18) - Fallback: `RELEASE_PLATFORM` не задан → body без секции (backward compat) - Fallback: секция CHANGELOG не найдена → весь CHANGELOG + warning (как в voice_assistant PR #27) - Fallback: `CHANGELOG.md` отсутствует → body = "" (релиз без нот, не падать) - Fallback: `pyproject.toml` отсутствует → ошибка (имя проекта обязательно) - Fallback: `RUNNER_OS` не задан → ошибка (ОС обязательна для asset filename) ## Граничные случаи - `pyproject.toml` без field `name` → ошибка с сообщением "field 'name' not found in pyproject.toml" - `RUNNER_OS=Windows` + `RUNNER_ARCH=X64` → `windows-x64` (lowercase) - `RUNNER_OS=Linux` + `RUNNER_ARCH=X64` → `linux-x64` - `RUNNER_OS=macOS` + `RUNNER_ARCH=ARM64` → `macos-arm64` (lowercase os, но `macOS` → `macos`) - `RUNNER_OS=macOS` + `RUNNER_ARCH=X64` → `macos-x64` (Intel Mac) - `RELEASE_PLATFORM=""` (пустая строка) → body без секции (backward compat, как omitted) - `RELEASE_PLATFORM="Windows 10+ (64-bit)"` → body с секцией - Forgejo API: release для тега уже существует → delete then create (idempotent) - Forgejo API: attachment upload fails → релиз создан, но ассет не загружен → залогировать ошибку, не падать (релиз без ассета лучше чем нет релиза) - CHANGELOG с тире `—` вместо дефиса `-` в заголовке (`## [0.1.0] — 2026-08-11`) → regex покрывает оба - Asset filename с дефисом в имени проекта: `voice-assistant` → `voice-assistant-windows-x64.zip` (не двойной дефис) ## Влияние на связанные компоненты - `skills/release/SKILL.md` — основной файл обновления - `.opencode/scripts/create_release.py` — новый файл - `.opencode/scripts/release_notes.py` — новый файл - 下游 репо (voice_assistant, video_uniq) — отдельные issues для миграции на глобальный `create_release.py`: - voice_assistant: issue (отдельный) — удалить локальный `scripts/create_release.py`, скачать глобальный через curl в `release.yml`, добавить `RELEASE_PLATFORM` env var - video_uniq: issue (отдельный, будущее) — аналогично - `create-changelog` tool — НЕ трогать (issue #63 closed, `platform` arg уже удалён) - `skills/repo-readme/SKILL.md` — НЕ трогать ## Вне scope - Миграция voice_assistant на глобальный `create_release.py` — отдельный issue в voice_assistant - Миграция video_uniq — отдельный issue в video_uniq (будущее) - Reusable workflow / composite action — НЕ в этом issue (выбран download через curl, проще) - Multi-platform support (Linux/macOS jobs) — будущее, стандарт описан в SKILL.md но реализация в каждом репо отдельно - PATCH существующего релиза v0.1.0 в voice_assistant — отдельная операция после merge миграции voice_assistant - `release_notes.py` тесты — опционально, если opencode-config имеет тест-инфраструктуру для Python скриптов ## Критерии приемки - [ ] `.opencode/scripts/create_release.py` создан — self-contained (stdlib only), читает pyproject.toml name, парсит RUNNER_OS/ARCH, формирует asset filename `{name}-{os}-{arch}.zip`, формирует body с секцией "Системные требования" из `RELEASE_PLATFORM`, idempotent (delete+create) - [ ] `.opencode/scripts/release_notes.py` создан — self-contained (stdlib only), `extract_changelog_section(path, tag)`, 3 fallback'а (секция не найдена, CHANGELOG отсутствует, пустая секция) - [ ] `skills/release/SKILL.md` — раздел «Платформа в релизе (детерминированный стандарт)» добавлен - [ ] SKILL.md — пример release.yml с curl-download `create_release.py` + `RELEASE_PLATFORM` env var - [ ] SKILL.md — описан формат asset filename `{name}-{os}-{arch}.zip` - [ ] SKILL.md — описан формат body (секция "Системные требования" + bullets) - [ ] SKILL.md — описаны правила: min ОС поднимается → CHANGELOG `### Изменено` + обновить `RELEASE_PLATFORM`; multi-platform → отдельный job на ОС - [ ] SKILL.md — описаны anti-patterns: НЕ `**Платформы:**` заголовок, НЕ Python в требованиях, НЕ платформа в CHANGELOG - [ ] Старое упоминание `platform` arg удалено из SKILL.md (если осталось после issue #63)
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#65
No description provided.