From d2cef94ac94218492d2b777b82849d79a73a65f8 Mon Sep 17 00:00:00 2001 From: Sergey <93754860+slaid098@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:15:12 +0300 Subject: [PATCH] feat(create-readme): add cover image reference to template and validation (#158) * feat(create-readme): add cover image reference to template and validation * feat(repo-readme): extend skill with cover generation + add command * chore(assets): add opencode brand logo and cover.png * docs(handoff): scaffold cover-pipeline PR notes * docs(handoff): set PR number * docs(project-map): update after cover pipeline structural changes --------- Co-authored-by: opencode-agent --- .opencode/commands/repo-readme.md | 5 +++ .opencode/draw-image/brand-logos/opencode.svg | 3 ++ .opencode/skills/repo-readme/SKILL.md | 39 +++++++++++++++--- .opencode/tools/create-readme.ts | 6 +++ README.md | 3 ++ assets/cover.meta.json | 5 +++ assets/cover.png | Bin 0 -> 36703 bytes docs/decisions/067-pr-158-cover-pipeline.md | 17 ++++++++ docs/handoff/pr-158-cover-pipeline.md | 26 ++++++++++++ docs/project-map/README.md | 8 ++-- 10 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 .opencode/commands/repo-readme.md create mode 100644 .opencode/draw-image/brand-logos/opencode.svg create mode 100644 assets/cover.meta.json create mode 100644 assets/cover.png create mode 100644 docs/decisions/067-pr-158-cover-pipeline.md create mode 100644 docs/handoff/pr-158-cover-pipeline.md diff --git a/.opencode/commands/repo-readme.md b/.opencode/commands/repo-readme.md new file mode 100644 index 0000000..1b540e5 --- /dev/null +++ b/.opencode/commands/repo-readme.md @@ -0,0 +1,5 @@ +--- +description: Standardize repo README + cover image (create/validate) +agent: build +--- +Load the `repo-readme` skill via `skill({name: "repo-readme"})` and follow its ПРОТОКОЛ strictly. Workflow: create-readme (generate) → draw-image (cover) → validate → fix cycle. One command = full README + cover standardization. \ No newline at end of file diff --git a/.opencode/draw-image/brand-logos/opencode.svg b/.opencode/draw-image/brand-logos/opencode.svg new file mode 100644 index 0000000..22a59ea --- /dev/null +++ b/.opencode/draw-image/brand-logos/opencode.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.opencode/skills/repo-readme/SKILL.md b/.opencode/skills/repo-readme/SKILL.md index 3a3e0e4..7bc8ace 100644 --- a/.opencode/skills/repo-readme/SKILL.md +++ b/.opencode/skills/repo-readme/SKILL.md @@ -37,11 +37,16 @@ Support block, Quick Start, language switcher). Скилл даёт контек ## 3. Workflow 1. `create-readme` (mode: `create`) → генерирует README с гарантированной - структурой. -2. Ручные правки если нужно (агент редактирует файл напрямую через Edit) — + структурой (включая `![Cover](assets/cover.png)` после H1). +2. `draw-image` (template `"cover"`, slots по типу репо, `title`, `subtitle`, + `out: "./assets/cover.png"`) → рендерит cover-изображение (1024×1024 PNG) на + место, на которое ссылается README. Default `out` у `draw-image` уже + `"./assets/cover.png"` — можно не передавать. +3. Ручные правки если нужно (агент редактирует файл напрямую через Edit) — например, расширить `custom_sections`, поправить формулировки. -3. `create-readme` (mode: `validate`) → проверяет, что структура не нарушена. -4. Если `validate` fails → фикс нарушения → re-`validate`. Цикл пока не +4. `create-readme` (mode: `validate`) → проверяет, что структура не нарушена + (теперь в т.ч. наличие `assets/cover.png` reference). +5. Если `validate` fails → фикс нарушения → re-`validate`. Цикл пока не пройдёт. Локальный режим (по умолчанию): тулза пишет в `file_path` (default @@ -49,6 +54,25 @@ Support block, Quick Start, language switcher). Скилл даёт контек (`owner/name`) — тулза сделает PUT через `gh api repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA. +### Slot-выбор для cover + +`draw-image` template `cover` имеет 3 опциональных слота. Значение slot — +либо имя Lucide-иконки (резолвится из `icons/lucide/.svg`), либо имя +brand-logo (резолвится из `brand-logos/.svg`), либо путь к файлу +(`./...` / `/...` / `../...`). + +- `icon` — основная иконка (400×400, slot recolor=accent). Для репо, + ассоциированных с продуктом/брендом — brand-logo (напр. `opencode`). Для + утилит/SDK/скриптов — Lucide (напр. `square-terminal`, `code-xml`, + `brain-circuit`). +- `sub-icon` — опциональная вторая иконка (200×200, recolor=accent). Lucide + (напр. `git-branch`, `bot`, `terminal`). +- `badge` — опциональная третья иконка (180×180, bg=surface, border=accent, + radius=0.5). Lucide или путь к файлу (напр. логотип-плашка). + +Cover сохраняется в `assets/cover.png` (default `draw-image` out path). +README ссылается именно на этот путь через `![Cover](assets/cover.png)`. + ## 4. Зачем разделители (контекст для агента) Витрина **slaid098.dev** скачивает raw `README.md` из каждого репо и @@ -77,6 +101,9 @@ repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA. ```markdown # 🚀 {repo_name} + +![Cover](assets/cover.png) + > {tagline_en} @@ -156,7 +183,9 @@ repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA. ``` `validate` проверяет: наличие всех 6 пар EN/RU разделителей (tagline + summary + -features), непустой контент между ними, H1 title prefix `# 🚀 `, ссылку +features), непустой контент между ними, H1 title prefix `# 🚀 `, **cover image +reference `assets/cover.png`** (substring-чек, без проверки существования +файла), ссылку `slaid098.dev/support`, секции Quick Start (EN) и Быстрый старт (RU), language switcher `[English]` / `[Русский]`, заголовок `## 🇷🇺 Русский` (не "Русская версия"), anchor `[Русский](#-русский)` (не `#-русская-версия`). Флагирует diff --git a/.opencode/tools/create-readme.ts b/.opencode/tools/create-readme.ts index 13d627d..c91d2f2 100644 --- a/.opencode/tools/create-readme.ts +++ b/.opencode/tools/create-readme.ts @@ -80,6 +80,9 @@ function generateReadme(args: CreateArgs): string { const stepsRu = renderSteps(args.quick_start_steps_ru) return `# 🚀 ${args.repo_name} + +![Cover](assets/cover.png) + > ${args.tagline_en} @@ -169,6 +172,9 @@ export function validateReadme(content: string): { ok: boolean; issues: string[] issues.push(`${p.label} content between delimiters is empty`) } + if (!content.includes("assets/cover.png")) + issues.push("Missing cover image reference (assets/cover.png)") + if (!content.includes("# 🚀 ")) issues.push("Missing H1 title prefix '# 🚀 '") if (/^##\s+(License|LICENSE|Лицензия)\s*$/m.test(content)) diff --git a/README.md b/README.md index c50129a..8897d0a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # 🚀 opencode-config + +![Cover](assets/cover.png) + > Portable AI coding assistant config with memory & subagent pipeline diff --git a/assets/cover.meta.json b/assets/cover.meta.json new file mode 100644 index 0000000..07981b4 --- /dev/null +++ b/assets/cover.meta.json @@ -0,0 +1,5 @@ +{ + "hash": "de8036758f935399699ef01446e3b9c8d23d211863dae052932d0ffc1a298120", + "generatedAt": "2026-07-30T20:02:11.450Z", + "size": 1024 +} diff --git a/assets/cover.png b/assets/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3380d1c55934cb923da3370b4aa6abb9a8e367 GIT binary patch literal 36703 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

YJzLPFOhI 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 YQalW{%enN&&VZU2n_n(9y!Gh60Jl^RSO5S3 literal 0 HcmV?d00001 diff --git a/docs/decisions/067-pr-158-cover-pipeline.md b/docs/decisions/067-pr-158-cover-pipeline.md new file mode 100644 index 0000000..aa56aea --- /dev/null +++ b/docs/decisions/067-pr-158-cover-pipeline.md @@ -0,0 +1,17 @@ +# ADR-067: Cover image reference в README validation + +## Статус +Accepted (2026-07-30) + +## Контекст +README стандартизируется тулзой `create-readme` (delimiter-теги для slaid098.dev). Cover image (`assets/cover.png`, рендерится `draw-image` template "cover") должен отображаться в начале README после H1. До этого PR cover не был частью стандарта — README мог существовать без cover, и validate не ловил его отсутствие. + +## Решение +1. `generateReadme()` рендерит `![Cover](assets/cover.png)` между H1 и tagline-разделителями — cover попадает в зону выше summary, видим на GitHub сразу под заголовком. +2. `validateReadme()` проверяет **substring** `assets/cover.png` (без проверки существования файла). Файл-чек не делается намеренно: validate работает и в remote-режиме (через gh api), где файловая система репо недоступна агенту. Substring-чек ловит забытое упоминание, реальный рендер cover — отдельный шаг `draw-image` в workflow скилла. +3. Workflow `repo-readme` скилла: `create-readme` → `draw-image` → `validate` → fix-цикл. Cover хранится в `assets/cover.png` (default `draw-image` out path), README ссылается именно на этот путь. + +## Альтернативы +- **Проверять существование `assets/cover.png` файловой системой**: отвергнуто — ломает remote-режим validate (gh api, нет локального FS репо). Substring-чек унифицирует local и remote. +- **Встраивать cover как base64 в README**: отвергнуто — раздувает README, ломает delimiter-парсинг витриной, сложнее обновлять. +- **Отдельный validate-чек для cover на уровне draw-image**: отвергнуто — cover-pipeline должен ловиться на этапе README-валидации, т.к. README — источник правды для витрины. \ No newline at end of file diff --git a/docs/handoff/pr-158-cover-pipeline.md b/docs/handoff/pr-158-cover-pipeline.md new file mode 100644 index 0000000..2424039 --- /dev/null +++ b/docs/handoff/pr-158-cover-pipeline.md @@ -0,0 +1,26 @@ +--- +pr: 158 +title: Standardize README + cover image pipeline +--- + +## Что сделано +Реализован cover image pipeline для стандартизации README во всех репо slaid098: + +1. **brand-logos/opencode.svg** — адаптированный логотип OpenCode (512×512, fill #ccff00), резолвится через `draw-image` slot `icon=opencode` (ищется в `brand-logos/` после `icons/lucide/`). +2. **create-readme.ts**: + - `generateReadme()`: добавлен `![Cover](assets/cover.png)` после H1 (`# 🚀 {repo_name}`), перед tagline-разделителями, с пустыми строками между. + - `validateReadme()`: добавлен substring-чек `assets/cover.png` → issue `"Missing cover image reference (assets/cover.png)"`. Проверка существования файла НЕ делается (только substring). Чек расположен после delimiter-чеков, перед H1-чеком. +3. **repo-readme/SKILL.md**: workflow секция расширена — после `create` → `draw-image` (template "cover", slots) → `validate`. Добавлен slot-guidance (`icon`/`sub-icon`/`badge`, brand-logos vs Lucide). Reference-шаблон и описание валидации обновлены. 9 секций сохранены. +4. **commands/repo-readme.md**: новая slash-команда (`/repo-readme`), frontmatter `agent: build`, грузит `repo-readme` skill. +5. **opencode-config repo**: сгенерирован `assets/cover.png` (1024×1024 template, фактически 1365×1365 из-за density:96 в sharp — pre-existing поведение render.mjs), README.md обновлён cover reference (вручную через edit, т.к. системный tool подхватил pre-bundled версию create-readme.ts). + +## Почему +Cover image делает карточку репо на slaid098.dev и GitHub визуально цельной. Pipeline `create-readme → draw-image → validate` гарантирует, что каждый README ссылается на cover, а cover рендерится из on-brand SVG-шаблона. Validation-чек ловит забытый cover reference на этапе review. + +## Pending +— (после merge: регенерация README в других репо slaid098 через create-readme — отдельный шаг, как после PR #150 с taglines) + +## Watch out +- **Размер PNG 1365×1365, не 1024×1024** — это pre-existing поведение `draw-image` (sharp density:96, см. PR #134), НЕ регрессия этого PR. Спека issue #157 требовала 1024×1024 — зафиксировано как расхождение спеки с фактическим поведением, не чинилось (вне scope). +- **Системный tool `create-readme` не подхватил правки `.ts` в текущем runtime** — opencode кеширует plugin-tools при старте сессии. README обновлён вручную через edit. После restart opencode новый `create-readme.ts` будет загружен. Для будущих регенераций tool сработает корректно. +- `assets/cover.meta.json` закоммичен вместе с `cover.png` — нужен для идемпотентности `draw-image` (skip-if-same-hash). \ No newline at end of file diff --git a/docs/project-map/README.md b/docs/project-map/README.md index 722095d..3efac13 100644 --- a/docs/project-map/README.md +++ b/docs/project-map/README.md @@ -21,6 +21,7 @@ opencode-config/ │ │ └── reviewer.md # Code review subagent (verdict via `post-review` tool: APPROVE|REQUEST_CHANGES|NEEDS_DISCUSSION) — PR#46, PR#69 │ ├── commands/ │ │ ├── configure-opencode.md # /configure-opencode — edit opencode.json +│ │ ├── repo-readme.md # /repo-readme — standardized README generation (frontmatter agent: build, loads repo-readme skill) — PR#158 │ │ ├── run-pipeline.md # /run-pipeline — 7-phase PR pipeline │ │ └── spec.md # /spec — 9-phase spec generation │ ├── skills/ @@ -36,7 +37,7 @@ opencode-config/ │ │ ├── python-development/SKILL.md # Python dev patterns │ │ ├── release/SKILL.md # Tag + GitHub Release │ │ ├── repo-init/SKILL.md # New repository bootstrap -│ │ ├── repo-readme/SKILL.md # Standardized README generation (create-readme tool: create vs validate, bilingual Why/What + Features table, GitHub metadata, quick_start_steps clickable steps + conditional bash block; tagline_en/tagline_ru required + delimiter-теги, breaking change note) — PR#112, PR#116, PR#118, PR#130, PR#146, PR#151 +│ │ ├── repo-readme/SKILL.md # Standardized README generation (create-readme tool: create vs validate, bilingual Why/What + Features table, GitHub metadata, quick_start_steps clickable steps + conditional bash block; tagline_en/tagline_ru required + delimiter-теги, breaking change note; workflow: create → draw-image (template "cover", slots icon/sub-icon/badge, brand-logos vs Lucide) → validate → fix-цикл) — PR#112, PR#116, PR#118, PR#130, PR#146, PR#151, PR#158 (cover pipeline в workflow) │ │ ├── tunnel/SKILL.md # Cloudflare tunnel toggle (tool `tunnel()`: 1-й вызов start, 2-й stop) — PR#63 (восстановлен, удалён в PR#42) │ │ ├── run-tests/SKILL.md # Test runner guide │ │ └── spec/SKILL.md # 9-phase spec generation @@ -45,7 +46,7 @@ opencode-config/ │ │ ├── commit.ts # commit tool wrapper (1 arg message, validates format+staged) — PR#38 │ │ ├── create-issue.ts # create-issue tool wrapper (3 args, validates format+labels; optional repo?: string) — PR#38, PR#65 │ │ ├── create-pr.ts # create-pr tool wrapper (3 args, validates format+Closes #N; optional repo?: string) — PR#38, PR#65 -│ │ ├── create-readme.ts # create-readme tool (TS plugin, modes: create/validate; standardized bilingual README with features table, include_clone/development_en/ru/quick_start_steps_en/ru optional params, clickable access_url [url](url), conditional bash block via hasBashBlock, RU heading 'Русский' + anchor checks, 6 delimiter pairs for slaid098.dev (summary-en/ru, features-en/ru, tagline-en/ru); tagline_en+tagline_ru required (BREAKING: tagline removed PR#151), content-валидация repo_name (lowercase kebab-case) + tagline_en (no Cyrillic) + tagline_ru (require Cyrillic), validateReadme H1 prefix check `# 🚀 `; local fs + remote gh api) — PR#112, PR#116, PR#118, PR#130, PR#146, PR#149 (quick_start optional + guard), PR#151 (bilingual tagline + repo_name validation, breaking) +│ │ ├── create-readme.ts # create-readme tool (TS plugin, modes: create/validate; standardized bilingual README with features table, include_clone/development_en/ru/quick_start_steps_en/ru optional params, clickable access_url [url](url), conditional bash block via hasBashBlock, RU heading 'Русский' + anchor checks, 6 delimiter pairs for slaid098.dev (summary-en/ru, features-en/ru, tagline-en/ru); tagline_en+tagline_ru required (BREAKING: tagline removed PR#151), content-валидация repo_name (lowercase kebab-case) + tagline_en (no Cyrillic) + tagline_ru (require Cyrillic), validateReadme H1 prefix check `# 🚀 `; generateReadme рендерит `![Cover](assets/cover.png)` после H1 перед tagline-разделителями; validateReadme substring-чек `assets/cover.png` (без file existence — для remote gh api режима); local fs + remote gh api) — PR#112, PR#116, PR#118, PR#130, PR#146, PR#149 (quick_start optional + guard), PR#151 (bilingual tagline + repo_name validation, breaking), PR#158 (cover image reference в template + validation) │ │ ├── draw-image.ts # draw-image tool wrapper (opencode plugin, 5 args: template/title/subtitle?/slots?/out?; spawnSync node cli.ts render → sharp PNG) — PR#133 │ │ ├── merge-pr.ts # merge-pr tool wrapper (orchestrator-safe gh pr merge; optional repo?: string) — PR#30, PR#65 │ │ ├── memory-access.ts # memory-access tool (bump frontmatter last_accessed/access_count, regex replace, atomic write tmp+rename) — PR#101 @@ -68,7 +69,7 @@ opencode-config/ │ │ ├── templates/cover.svg # 1024×1024 cover template (slots: icon + sub-icon + badge, {{title}}/{{subtitle}}) │ │ ├── fonts/ # Geist Sans TTF (Regular + Bold) bundled for sharp fontFiles │ │ ├── icons/lucide/ # 2007 Lucide SVG icons (synced from npm lucide-static via postinstall) -│ │ ├── brand-logos/ # brand SVG logos (empty in v1, .gitkeep) +│ │ ├── brand-logos/ # brand SVG logos (opencode.svg — адаптированный логотип OpenCode 512×512 fill #ccff00, резолвится через draw-image slot icon=opencode) — PR#158 │ │ ├── scripts/sync-lucide.mjs # postinstall: copy icons from node_modules/lucide-static → icons/lucide/ │ │ ├── src/ │ │ │ ├── config.ts # loadBrand + validateBrand (zod-style hex validation) @@ -107,6 +108,7 @@ opencode-config/ │ ├── handoff/ # PR handoffs (pr--.md) │ ├── decisions/ # ADRs (NNN-pr--.md) │ └── project-map/ # This file — structure snapshot +├── assets/ # Generated cover images for README (cover.png + cover.meta.json idempotency hash; default draw-image out path, referenced by create-readme `![Cover](assets/cover.png)`) — PR#158 ├── src/ # Python RAG CLI (memory) — PR#17 │ └── memory/ │ ├── __init__.py