fix(create-readme): allow empty quick_start with steps-only Quick Start (#149)

* fix(create-readme): allow empty quick_start with steps-only Quick Start

* docs(handoff): add handoff + ADR for empty-quick-start

* docs(handoff): set PR number

* docs(project-map): update create-readme.ts role for PR#149

---------

Co-authored-by: opencode-agent <agent@opencode.local>
This commit is contained in:
Sergey 2026-07-30 04:06:52 +03:00 committed by GitHub
parent 8308de0981
commit 4bb504a604
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 102 additions and 8 deletions

View file

@ -13,7 +13,7 @@ type CreateArgs = {
what_en: string
why_ru: string
what_ru: string
quick_start: string
quick_start?: string
features_en: Feature[]
features_ru: Feature[]
access_url?: string
@ -67,12 +67,13 @@ function generateReadme(args: CreateArgs): string {
const cloneLine = args.include_clone !== false
? `git clone https://github.com/slaid098/${args.repo_name}.git\n`
: ""
const hasBashBlock = args.include_clone !== false || args.quick_start !== ""
const hasBashBlock = args.include_clone !== false || (args.quick_start ?? "") !== ""
const qs = args.quick_start ?? ""
const bashBlockEn = hasBashBlock
? `\`\`\`bash\n${cloneLine}${args.quick_start}\n\`\`\``
? `\`\`\`bash\n${cloneLine}${qs}\n\`\`\``
: ""
const bashBlockRu = hasBashBlock
? `\`\`\`bash\n${cloneLine}${args.quick_start}\n\`\`\``
? `\`\`\`bash\n${cloneLine}${qs}\n\`\`\``
: ""
const stepsEn = renderSteps(args.quick_start_steps_en)
const stepsRu = renderSteps(args.quick_start_steps_ru)
@ -210,7 +211,7 @@ export default tool({
quick_start: tool.schema
.string()
.optional()
.describe("Install/setup command (e.g. 'pip install -r requirements.txt'). Required for create mode."),
.describe("Install/setup command (e.g. 'pip install -r requirements.txt'). Optional — omit or pass '' when setup is steps-only (use quick_start_steps_*); bash block omitted when empty AND include_clone is false."),
quick_start_steps_en: tool.schema
.array(tool.schema.string())
.optional()
@ -294,7 +295,6 @@ export default tool({
what_en: args.what_en,
why_ru: args.why_ru,
what_ru: args.what_ru,
quick_start: args.quick_start,
}
for (const [k, v] of Object.entries(required)) {
if (!v) return `${k} is required for create mode`
@ -304,6 +304,14 @@ export default tool({
if (!args.features_ru || args.features_ru.length === 0)
return `❌ features_ru is required for create mode`
const hasBash = args.include_clone !== false || (args.quick_start ?? "") !== ""
const hasStepsEn = !!args.quick_start_steps_en && args.quick_start_steps_en.length > 0
const hasStepsRu = !!args.quick_start_steps_ru && args.quick_start_steps_ru.length > 0
if (!hasBash && !hasStepsEn)
return `❌ quick_start or quick_start_steps_en required (Quick Start EN would be empty)`
if (!hasBash && !hasStepsRu)
return `❌ quick_start or quick_start_steps_ru required (Quick Start RU would be empty)`
const content = generateReadme({
repo_name: args.repo_name!,
tagline: args.tagline!,
@ -311,7 +319,7 @@ export default tool({
what_en: args.what_en!,
why_ru: args.why_ru!,
what_ru: args.what_ru!,
quick_start: args.quick_start!,
quick_start: args.quick_start,
features_en: args.features_en!,
features_ru: args.features_ru!,
access_url: args.access_url,

View file

@ -0,0 +1,66 @@
# ADR-063: Allow empty quick_start with steps-only Quick Start (PR-149)
## Статус
Accepted (2026-07-30)
## Контекст
ADR-062 (PR-146) добавил параметры `quick_start_steps_en/ru` и условный
`hasBashBlock` (`include_clone !== false || quick_start !== ""`), чтобы
поддержать userscript-README с кликабельными шагами вместо bash-команды. Однако
валидация в `execute()` (mode `create`) по-прежнему требовала непустой
`quick_start` через falsy-чек `!v` в required-Record — пустая строка `""`
считалась отсутствующей, тулза возвращала `quick_start is required for create
mode`. Скилл `repo-readme` (раздел 8 «Кейс: userscript / web-app / npm-package»)
документирует комбинацию `include_clone: false` + `quick_start: ""` +
`quick_start_steps_*` как supported — но тулза её отвергала. Дополнительно:
`hasBashBlock` использовал `args.quick_start !== ""`, что для `undefined`
давало `true` (undefined !== ""), и bash-блок рендерил literal `undefined`
в теле code-fence. ADR-062 явно отметил в «Альтернативах», что ослабление
required-чек — отдельное решение вне scope того PR.
## Решение
1. **`CreateArgs.quick_start`** — сделан optional (`quick_start?: string`),
чтобы отражать реальную опциональность параметра.
2. **Required-валидация в `execute()`**`quick_start` убран из required-Record.
Вместо него добавлен guard после общих required-чеков:
- `hasBash = include_clone !== false || (quick_start ?? "") !== ""`
- `hasStepsEn = !!quick_start_steps_en && length > 0`
- `hasStepsRu = !!quick_start_steps_ru && length > 0`
- Если `!hasBash && !hasStepsEn` → ошибка `quick_start or
quick_start_steps_en required (Quick Start EN would be empty)`
- Если `!hasBash && !hasStepsRu` → аналогичная ошибка для RU.
Guard гарантирует, что Quick Start секция не пуста в ОБОИХ языках. Если
bash-блок рендерится — steps опциональны (backward compat).
3. **`hasBashBlock` и тела bash-блоков** — `args.quick_start ?? ""` вместо
`args.quick_start`: обрабатывает `undefined` (когда параметр не передан
вообще), иначе `undefined !== ""` даёт `true` и bash-блок рендерит literal
`undefined`. Локальная переменная `qs = args.quick_start ?? ""` переиспользуется
в обоих bash-блоках (EN/RU).
4. **Schema `.describe()`** — убрано "Required for create mode.", добавлено
пояснение: параметр optional, пустая строка/absent допустимы когда есть
`quick_start_steps_*`, bash-блок опускается при пустом AND `include_clone=false`.
5. **`validateReadme`** — без изменений: проверяет наличие строк "Quick Start" /
"Быстрый старт" в заголовках (`### ⚡ Quick Start` / `### ⚡ Быстрый старт`),
которые рендерятся всегда. README без bash-блока (только steps) проходит
валидацию. Подтверждено smoke-тестом (сценарий 1).
6. **Backward compatibility** — все существующие вызовы с непустым `quick_start`
и без steps работают идентично (сценарий 2 smoke-теста: git clone + pip
install, без шагов, без `undefined`).
## Альтернативы
- **Сделать `quick_start` required только когда нет steps** — отвергнуто:
сложная условная required-логика в одном Record, тяжело читать. Cleaner —
убрать из required и добавить явный guard с понятными сообщениями ошибок
для каждого языка отдельно.
- **Один общий guard «есть bash ИЛИ (steps EN И steps RU)»** — отвергнуто:
это допустило бы пустую RU-секцию когда есть bash + steps EN (но это
backward-compat кейс, где bash рендерится в обеих секциях). Текущий guard
(`!hasBash && !hasStepsX`) срабатывает ТОЛЬКО когда bash-блока нет —
тогда steps нужны в обоих языках. Когда bash есть — steps опциональны.
- **Валидировать steps в `validateReadme`** — отвергнуто: validate проверяет
структуру delimiter-пар и наличие секций, не содержимое Quick Start.
Заголовки рендерятся всегда; проверять пустоту секции — responsibility
`execute()`, не `validateReadme`.
- **Не вводить `qs` локальную переменную** — отвергнуто: повторение
`args.quick_start ?? ""` в трёх местах (hasBashBlock + 2 bash-блока) хуже
для читаемости, чем одна переменная.

View file

@ -0,0 +1,20 @@
---
pr: 149
title: fix(create-readme): allow empty quick_start with steps-only Quick Start
---
## Что сделано
Ослаблена валидация `quick_start` в `.opencode/tools/create-readme.ts` (mode `create`): параметр убран из required-Record (раньше falsy-чек `!v` отвергал `""`), вместо него добавлен guard — Quick Start секция не должна быть пустой (bash-блок ИЛИ steps в ОБОИХ языках EN/RU). `CreateArgs.quick_start` сделан optional (`?: string`). `hasBashBlock` и тела bash-блоков обрабатывают `undefined` через `?? ""` (раньше `undefined !== ""` давало `true` → bash-блок рендерил literal `undefined`). `.describe()` в schema обновлён (убрано "Required for create mode."). Smoke-тест: 5 сценариев, 24 assertion'а, все PASS (userscript-only-steps, backward-compat bash, guard EN, guard RU, bash+steps-en-no-steps-ru).
## Почему
Issue #147: тулза блокировала userscript-README без bash-команды (`quick_start: ""` + `include_clone: false`), хотя скилл `repo-readme` (раздел 8) документирует именно эту комбинацию. PR #146 (ADR-062) добавил `quick_start_steps_*` и условный `hasBashBlock`, но явно отметил в «Альтернативах», что ослабление required-валидации `quick_start` — вне scope того PR. PR #147 закрывает этот пробел: guard гарантирует, что Quick Start не пуст, при этом пустой `quick_start` (или его отсутствие) теперь допустим, если есть steps.
## Pending
## Watch out
Guard проверяет steps отдельно для EN и RU: если bash-блок не рендерится (`include_clone=false` + пустой `quick_start`), нужно `quick_start_steps_en` И `quick_start_steps_ru` — иначе одна из секций будет пустой. Если у репо один bash-блок (non-empty `quick_start` или `include_clone=true`) — steps опциональны в обоих языках (backward compat). `validateReadme` не менялся: он проверяет наличие строк "Quick Start"/"Быстрый старт" в заголовках, которые рендерятся всегда. ADR-063 отменяет альтернативу «Ослабить required-чек quick_start» из ADR-062.

View file

@ -45,7 +45,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, 4 delimiter pairs for slaid098.dev; local fs + remote gh api) — PR#112, PR#116, PR#118, PR#130, PR#146
│ │ ├── 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, 4 delimiter pairs for slaid098.dev; local fs + remote gh api) — PR#112, PR#116, PR#118, PR#130, PR#146, PR#149 (quick_start optional + guard: Quick Start non-empty per-lang via hasBash/hasStepsEn/hasStepsRu; hasBashBlock handles undefined via ?? "")
│ │ ├── 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