diff --git a/.opencode/tools/create-readme.ts b/.opencode/tools/create-readme.ts index 9b9e7e7..97813f6 100644 --- a/.opencode/tools/create-readme.ts +++ b/.opencode/tools/create-readme.ts @@ -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, diff --git a/docs/decisions/063-pr-149-empty-quick-start.md b/docs/decisions/063-pr-149-empty-quick-start.md new file mode 100644 index 0000000..657fb1d --- /dev/null +++ b/docs/decisions/063-pr-149-empty-quick-start.md @@ -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-блока) хуже + для читаемости, чем одна переменная. \ No newline at end of file diff --git a/docs/handoff/pr-149-empty-quick-start.md b/docs/handoff/pr-149-empty-quick-start.md new file mode 100644 index 0000000..f667bc7 --- /dev/null +++ b/docs/handoff/pr-149-empty-quick-start.md @@ -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. \ No newline at end of file diff --git a/docs/project-map/README.md b/docs/project-map/README.md index f67b90b..3e281b6 100644 --- a/docs/project-map/README.md +++ b/docs/project-map/README.md @@ -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