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 <agent@opencode.local>
This commit is contained in:
Sergey 2026-07-30 23:15:12 +03:00 committed by GitHub
parent 556a5b57eb
commit d2cef94ac9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 104 additions and 8 deletions

View file

@ -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.

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M384 416H128V96H384V416ZM320 160H192V352H320V160Z" fill="#ccff00"/>
</svg>

After

Width:  |  Height:  |  Size: 225 B

View file

@ -37,11 +37,16 @@ Support block, Quick Start, language switcher). Скилл даёт контек
## 3. Workflow ## 3. Workflow
1. `create-readme` (mode: `create`) → генерирует README с гарантированной 1. `create-readme` (mode: `create`) → генерирует README с гарантированной
структурой. структурой (включая `![Cover](assets/cover.png)` после H1).
2. Ручные правки если нужно (агент редактирует файл напрямую через Edit) — 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`, поправить формулировки. например, расширить `custom_sections`, поправить формулировки.
3. `create-readme` (mode: `validate`) → проверяет, что структура не нарушена. 4. `create-readme` (mode: `validate`) → проверяет, что структура не нарушена
4. Если `validate` fails → фикс нарушения → re-`validate`. Цикл пока не (теперь в т.ч. наличие `assets/cover.png` reference).
5. Если `validate` fails → фикс нарушения → re-`validate`. Цикл пока не
пройдёт. пройдёт.
Локальный режим (по умолчанию): тулза пишет в `file_path` (default Локальный режим (по умолчанию): тулза пишет в `file_path` (default
@ -49,6 +54,25 @@ Support block, Quick Start, language switcher). Скилл даёт контек
(`owner/name`) — тулза сделает PUT через `gh api (`owner/name`) — тулза сделает PUT через `gh api
repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA. repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA.
### Slot-выбор для cover
`draw-image` template `cover` имеет 3 опциональных слота. Значение slot —
либо имя Lucide-иконки (резолвится из `icons/lucide/<name>.svg`), либо имя
brand-logo (резолвится из `brand-logos/<name>.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. Зачем разделители (контекст для агента) ## 4. Зачем разделители (контекст для агента)
Витрина **slaid098.dev** скачивает raw `README.md` из каждого репо и Витрина **slaid098.dev** скачивает raw `README.md` из каждого репо и
@ -77,6 +101,9 @@ repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA.
```markdown ```markdown
# 🚀 {repo_name} # 🚀 {repo_name}
![Cover](assets/cover.png)
<!-- tagline-en:start --> <!-- tagline-en:start -->
> {tagline_en} > {tagline_en}
<!-- tagline-en:end --> <!-- tagline-en:end -->
@ -156,7 +183,9 @@ repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA.
``` ```
`validate` проверяет: наличие всех 6 пар EN/RU разделителей (tagline + summary + `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 `slaid098.dev/support`, секции Quick Start (EN) и Быстрый старт (RU), language
switcher `[English]` / `[Русский]`, заголовок `## 🇷🇺 Русский` (не "Русская switcher `[English]` / `[Русский]`, заголовок `## 🇷🇺 Русский` (не "Русская
версия"), anchor `[Русский](#-русский)` (не `#-русская-версия`). Флагирует версия"), anchor `[Русский](#-русский)` (не `#-русская-версия`). Флагирует

View file

@ -80,6 +80,9 @@ function generateReadme(args: CreateArgs): string {
const stepsRu = renderSteps(args.quick_start_steps_ru) const stepsRu = renderSteps(args.quick_start_steps_ru)
return `# 🚀 ${args.repo_name} return `# 🚀 ${args.repo_name}
![Cover](assets/cover.png)
<!-- tagline-en:start --> <!-- tagline-en:start -->
> ${args.tagline_en} > ${args.tagline_en}
<!-- tagline-en:end --> <!-- tagline-en:end -->
@ -169,6 +172,9 @@ export function validateReadme(content: string): { ok: boolean; issues: string[]
issues.push(`${p.label} content between delimiters is empty`) 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("# 🚀 ")) if (!content.includes("# 🚀 "))
issues.push("Missing H1 title prefix '# 🚀 '") issues.push("Missing H1 title prefix '# 🚀 '")
if (/^##\s+(License|LICENSE|Лицензия)\s*$/m.test(content)) if (/^##\s+(License|LICENSE|Лицензия)\s*$/m.test(content))

View file

@ -1,4 +1,7 @@
# 🚀 opencode-config # 🚀 opencode-config
![Cover](assets/cover.png)
<!-- tagline-en:start --> <!-- tagline-en:start -->
> Portable AI coding assistant config with memory & subagent pipeline > Portable AI coding assistant config with memory & subagent pipeline
<!-- tagline-en:end --> <!-- tagline-en:end -->

5
assets/cover.meta.json Normal file
View file

@ -0,0 +1,5 @@
{
"hash": "de8036758f935399699ef01446e3b9c8d23d211863dae052932d0ffc1a298120",
"generatedAt": "2026-07-30T20:02:11.450Z",
"size": 1024
}

BIN
assets/cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -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 — источник правды для витрины.

View file

@ -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).

View file

@ -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 │ │ └── reviewer.md # Code review subagent (verdict via `post-review` tool: APPROVE|REQUEST_CHANGES|NEEDS_DISCUSSION) — PR#46, PR#69
│ ├── commands/ │ ├── commands/
│ │ ├── configure-opencode.md # /configure-opencode — edit opencode.json │ │ ├── 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 │ │ ├── run-pipeline.md # /run-pipeline — 7-phase PR pipeline
│ │ └── spec.md # /spec — 9-phase spec generation │ │ └── spec.md # /spec — 9-phase spec generation
│ ├── skills/ │ ├── skills/
@ -36,7 +37,7 @@ opencode-config/
│ │ ├── python-development/SKILL.md # Python dev patterns │ │ ├── python-development/SKILL.md # Python dev patterns
│ │ ├── release/SKILL.md # Tag + GitHub Release │ │ ├── release/SKILL.md # Tag + GitHub Release
│ │ ├── repo-init/SKILL.md # New repository bootstrap │ │ ├── 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) │ │ ├── tunnel/SKILL.md # Cloudflare tunnel toggle (tool `tunnel()`: 1-й вызов start, 2-й stop) — PR#63 (восстановлен, удалён в PR#42)
│ │ ├── run-tests/SKILL.md # Test runner guide │ │ ├── run-tests/SKILL.md # Test runner guide
│ │ └── spec/SKILL.md # 9-phase spec generation │ │ └── 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 │ │ ├── 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-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-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 │ │ ├── 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 │ │ ├── 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 │ │ ├── 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}}) │ │ ├── templates/cover.svg # 1024×1024 cover template (slots: icon + sub-icon + badge, {{title}}/{{subtitle}})
│ │ ├── fonts/ # Geist Sans TTF (Regular + Bold) bundled for sharp fontFiles │ │ ├── fonts/ # Geist Sans TTF (Regular + Bold) bundled for sharp fontFiles
│ │ ├── icons/lucide/ # 2007 Lucide SVG icons (synced from npm lucide-static via postinstall) │ │ ├── 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/ │ │ ├── scripts/sync-lucide.mjs # postinstall: copy icons from node_modules/lucide-static → icons/lucide/
│ │ ├── src/ │ │ ├── src/
│ │ │ ├── config.ts # loadBrand + validateBrand (zod-style hex validation) │ │ │ ├── config.ts # loadBrand + validateBrand (zod-style hex validation)
@ -107,6 +108,7 @@ opencode-config/
│ ├── handoff/ # PR handoffs (pr-<N>-<slug>.md) │ ├── handoff/ # PR handoffs (pr-<N>-<slug>.md)
│ ├── decisions/ # ADRs (NNN-pr-<N>-<slug>.md) │ ├── decisions/ # ADRs (NNN-pr-<N>-<slug>.md)
│ └── project-map/ # This file — structure snapshot │ └── 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 ├── src/ # Python RAG CLI (memory) — PR#17
│ └── memory/ │ └── memory/
│ ├── __init__.py │ ├── __init__.py