From ca2a04ce47d88ad1923f2ec9d2d4f7d59096a283 Mon Sep 17 00:00:00 2001 From: Sergey <93754860+slaid098@users.noreply.github.com> Date: Wed, 5 Aug 2026 04:15:43 +0300 Subject: [PATCH] refactor(templates): drop README from cookiecutter templates (#272) * refactor(templates): remove README.md from cookiecutter templates * test(templates): drop README expectations, expect WARN instead * docs(skills): note README is generated post-init via repo-readme * docs(skills): repo-readme creates README from scratch post-init * fix(templates): drop readme field from pyproject after README removal --------- Co-authored-by: opencode-agent --- .opencode/skills/project-template/SKILL.md | 14 ++- .opencode/skills/repo-readme/SKILL.md | 7 +- .../{{cookiecutter.project_name}}/README.md | 67 -------------- .../pyproject.toml | 1 - .../{{cookiecutter.project_name}}/README.md | 65 ------------- .../pyproject.toml | 1 - .../{{cookiecutter.project_name}}/README.md | 67 -------------- .../backend/pyproject.toml | 1 - tests/test_cookiecutter_templates.py | 92 ++++++++----------- 9 files changed, 55 insertions(+), 260 deletions(-) delete mode 100644 .opencode/templates/backend/{{cookiecutter.project_name}}/README.md delete mode 100644 .opencode/templates/cli/{{cookiecutter.project_name}}/README.md delete mode 100644 .opencode/templates/fullstack/{{cookiecutter.project_name}}/README.md diff --git a/.opencode/skills/project-template/SKILL.md b/.opencode/skills/project-template/SKILL.md index 7e645d3..b1fea9a 100644 --- a/.opencode/skills/project-template/SKILL.md +++ b/.opencode/skills/project-template/SKILL.md @@ -112,6 +112,11 @@ Tool вернёт отчёт: `Project: `, 7 групп `[OK]/[WARN]/[FAIL `Рекомендации:`. Покажи отчёт юзеру. Если tool вернул `⚠️ ...failed` → WARN, продолжай без отчёта. +> README у свежего проекта отсутствует (issue #269) — README генерируется +> позже через repo-readme skill по ручному вызову. Поэтому группа README +> даст WARN «README.md отсутствует» (не FAIL, exit code 0 в non-blocking +> режиме). Это ожидаемое поведение, не ошибка — упомяни в отчёте. + ### Шаг 6: Финальный репорт ``` @@ -119,6 +124,7 @@ Project created at .//. GitHub: https://github.com// Branch protection: main (PR + required_status_checks + linear history) Project-status: +README: отсутствует (WARN у project-status) — появится позже через repo-readme Дальше: /run-pipeline для реализации фич, или /spec для генерации spec. ``` @@ -206,6 +212,11 @@ Template FIX (ниже), передав путь и описание пробл заблокирован deny). 7. Верни: "done: project created at .//, git init + initial commit". +README.md в рендеренном проекте НЕТ (issue #269) — шаблоны его больше не +содержат. README появится позже через repo-readme skill по ручному вызову +(сначала юзер смотрит контент и cover.png из draw-image). project-status на +свежем проекте даст WARN по отсутствующему README — это ожидаемо, не чинить. + Если найдёшь баг вне scope — загрузи skill `bug-discovery` через `skill("bug-discovery")` и следуй протоколу. НЕ чини баг сам. ``` @@ -331,7 +342,8 @@ Cookiecutter templates доступны для: `backend`, `fullstack`, `cli` - `project-status` tool — read-only oracle, ALLOWED для оркестратора (как `pipeline-status` / `spec-status`). - init flow порядок: cookiecutter → git init/commit → gh repo create → branch - protection → project-status. Не меняй порядок. + protection → project-status. Не меняй порядок. README в init flow НЕ + генерируется — только по ручному вызову repo-readme (issue #269). - check flow: project-status → отчёт → рекомендации → subagents (последовательно). - Subagent error → 1 retry, потом STOP + report. - Совместим с spec-pipeline: Phase 8 EXECUTE (spec/SKILL.md Template I) может diff --git a/.opencode/skills/repo-readme/SKILL.md b/.opencode/skills/repo-readme/SKILL.md index 51942be..f1bfbf6 100644 --- a/.opencode/skills/repo-readme/SKILL.md +++ b/.opencode/skills/repo-readme/SKILL.md @@ -201,9 +201,10 @@ switcher `[English]` / `[Русский]`, заголовок `## 🇷🇺 Ру ## 6. Независимость от project-template - Скилл `project-template` (init flow) создаёт проект через cookiecutter — - шаблон уже включает `README.md` с базовой структурой. -- `repo-readme` (через тулзу `create-readme`) **наполняет** его - стандартизированным контентом (delimiter tags, bilingual, cover). + шаблоны README.md НЕ содержат (issue #269): свежий проект рождается без + README. +- `repo-readme` (через тулзу `create-readme`) **создаёт** полный README с нуля + (delimiter tags, bilingual, cover) по ручному вызову после init. - Может применяться к существующим репо без `project-template` — тулза перезапишет `README.md` (локально) или обновит через GitHub API (с SHA). diff --git a/.opencode/templates/backend/{{cookiecutter.project_name}}/README.md b/.opencode/templates/backend/{{cookiecutter.project_name}}/README.md deleted file mode 100644 index 26baca5..0000000 --- a/.opencode/templates/backend/{{cookiecutter.project_name}}/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# 🚀 {{ cookiecutter.project_name }} - -> Language switcher: **[English](#-english)** | **[Русский](#-русский)** - -![Cover](assets/cover.png) - - -{{ cookiecutter.description }} - - - -{{ cookiecutter.description }} - - -## 🇺🇸 English - - -- FastAPI backend with Tortoise ORM -- Pydantic-settings configuration -- Ruff + mypy strict + pytest -- Pre-commit hooks - - -### ⚡ Quick Start - -```bash -uv sync --extra dev -uv run uvicorn main:app --reload -``` - ---- - -## 💬 Support and contacts / Поддержка и контакты - -👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)** - ---- - -## 🇷🇺 Русский - - -{{ cookiecutter.description }} - - - -{{ cookiecutter.description }} - - - -- Бэкенд на FastAPI с Tortoise ORM -- Конфигурация через pydantic-settings -- Ruff + mypy strict + pytest -- Pre-commit хуки - - -### ⚡ Быстрый старт - -```bash -uv sync --extra dev -uv run uvicorn main:app --reload -``` - ---- - -## 💬 Support and contacts / Поддержка и контакты - -👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)** \ No newline at end of file diff --git a/.opencode/templates/backend/{{cookiecutter.project_name}}/pyproject.toml b/.opencode/templates/backend/{{cookiecutter.project_name}}/pyproject.toml index 7198523..0e537a2 100644 --- a/.opencode/templates/backend/{{cookiecutter.project_name}}/pyproject.toml +++ b/.opencode/templates/backend/{{cookiecutter.project_name}}/pyproject.toml @@ -6,7 +6,6 @@ build-backend = "hatchling.build" name = "{{ cookiecutter.project_name }}" version = "0.1.0" description = "{{ cookiecutter.description }}" -readme = "README.md" license = "MIT" requires-python = ">={{ cookiecutter.python_version }}" authors = [{ name = "slaid098" }] diff --git a/.opencode/templates/cli/{{cookiecutter.project_name}}/README.md b/.opencode/templates/cli/{{cookiecutter.project_name}}/README.md deleted file mode 100644 index 6a4d457..0000000 --- a/.opencode/templates/cli/{{cookiecutter.project_name}}/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# 🚀 {{ cookiecutter.project_name }} - -> Language switcher: **[English](#-english)** | **[Русский](#-русский)** - -![Cover](assets/cover.png) - - -{{ cookiecutter.description }} - - - -{{ cookiecutter.description }} - - -## 🇺🇸 English - - -- Typer-based CLI with rich output -- Ruff + mypy strict + pytest -- Pre-commit hooks - - -### ⚡ Quick Start - -```bash -uv sync --extra dev -{{ cookiecutter.project_name }} --help -``` - ---- - -## 💬 Support and contacts / Поддержка и контакты - -👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)** - ---- - -## 🇷🇺 Русский - - -{{ cookiecutter.description }} - - - -{{ cookiecutter.description }} - - - -- CLI на Typer с rich-выводом -- Ruff + mypy strict + pytest -- Pre-commit хуки - - -### ⚡ Быстрый старт - -```bash -uv sync --extra dev -{{ cookiecutter.project_name }} --help -``` - ---- - -## 💬 Support and contacts / Поддержка и контакты - -👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)** \ No newline at end of file diff --git a/.opencode/templates/cli/{{cookiecutter.project_name}}/pyproject.toml b/.opencode/templates/cli/{{cookiecutter.project_name}}/pyproject.toml index 27f5eb4..191e3e6 100644 --- a/.opencode/templates/cli/{{cookiecutter.project_name}}/pyproject.toml +++ b/.opencode/templates/cli/{{cookiecutter.project_name}}/pyproject.toml @@ -6,7 +6,6 @@ build-backend = "hatchling.build" name = "{{ cookiecutter.project_name }}" version = "0.1.0" description = "{{ cookiecutter.description }}" -readme = "README.md" license = "MIT" requires-python = ">={{ cookiecutter.python_version }}" authors = [{ name = "slaid098" }] diff --git a/.opencode/templates/fullstack/{{cookiecutter.project_name}}/README.md b/.opencode/templates/fullstack/{{cookiecutter.project_name}}/README.md deleted file mode 100644 index 2b78295..0000000 --- a/.opencode/templates/fullstack/{{cookiecutter.project_name}}/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# 🚀 {{ cookiecutter.project_name }} - -> Language switcher: **[English](#-english)** | **[Русский](#-русский)** - -![Cover](assets/cover.png) - - -{{ cookiecutter.description }} - - - -{{ cookiecutter.description }} - - -## 🇺🇸 English - - -- FastAPI backend (Tortoise ORM) + SvelteKit frontend (Svelte 5 runes) -- pydantic-settings configuration; JS frontend with Biome + Knip + Vitest + Playwright e2e -- Ruff + mypy strict + pytest (backend); SvelteKit + Vitest (frontend) -- Pre-commit hooks (backend); Biome + Knip (frontend) - - -### ⚡ Quick Start - -```bash -cd backend && uv sync --extra dev && uv run uvicorn main:app --reload -cd frontend && npm install && npm run dev -``` - ---- - -## 💬 Support and contacts / Поддержка и контакты - -👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)** - ---- - -## 🇷🇺 Русский - - -{{ cookiecutter.description }} - - - -{{ cookiecutter.description }} - - - -- Бэкенд FastAPI (Tortoise ORM) + фронтенд SvelteKit (Svelte 5 runes) -- Конфигурация через pydantic-settings; JS-фронтенд с Biome + Knip + Vitest + Playwright e2e -- Ruff + mypy strict + pytest (бэкенд); SvelteKit + Vitest (фронтенд) -- Pre-commit хуки (бэкенд); Biome + Knip (фронтенд) - - -### ⚡ Быстрый старт - -```bash -cd backend && uv sync --extra dev && uv run uvicorn main:app --reload -cd frontend && npm install && npm run dev -``` - ---- - -## 💬 Support and contacts / Поддержка и контакты - -👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)** \ No newline at end of file diff --git a/.opencode/templates/fullstack/{{cookiecutter.project_name}}/backend/pyproject.toml b/.opencode/templates/fullstack/{{cookiecutter.project_name}}/backend/pyproject.toml index 7198523..0e537a2 100644 --- a/.opencode/templates/fullstack/{{cookiecutter.project_name}}/backend/pyproject.toml +++ b/.opencode/templates/fullstack/{{cookiecutter.project_name}}/backend/pyproject.toml @@ -6,7 +6,6 @@ build-backend = "hatchling.build" name = "{{ cookiecutter.project_name }}" version = "0.1.0" description = "{{ cookiecutter.description }}" -readme = "README.md" license = "MIT" requires-python = ">={{ cookiecutter.python_version }}" authors = [{ name = "slaid098" }] diff --git a/tests/test_cookiecutter_templates.py b/tests/test_cookiecutter_templates.py index 73d2027..a19cf79 100644 --- a/tests/test_cookiecutter_templates.py +++ b/tests/test_cookiecutter_templates.py @@ -104,7 +104,6 @@ def test_backend_structure_full(render): ".python-version", "env.example", ".gitignore", - "README.md", "LICENSE", ".pre-commit-config.yaml", ".github/workflows/ci.yml", @@ -201,7 +200,6 @@ def test_cli_structure(render): expected = [ "pyproject.toml", ".python-version", - "README.md", "LICENSE", ".github/workflows/ci.yml", "src/cl/__init__.py", @@ -263,7 +261,6 @@ def test_fullstack_structure(render): # removed: counter.svelte.js, Header.svelte, jsconfig.json, +page.js # root CI runs both assert (render / ".github/workflows/ci.yml").exists() - assert (render / "README.md").exists() @pytest.mark.parametrize( @@ -400,46 +397,6 @@ def test_fullstack_uses_typescript(render): assert "tw-animate" not in deps or "tw-animate-css" in deps -# ── README delimiter tags (create-readme standard) ────────────────────────── - - -README_REQUIRED_TAGS = [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", -] - - -@pytest.mark.parametrize( - "template_name, extra_context", - [ - ("backend", {"project_name": "be"}), - ("cli", {"project_name": "cl"}), - ("fullstack", {"project_name": "fs"}), - ], -) -def test_readme_has_delimiter_tags(render): - """README must contain all 12 delimiter tags + standard headers.""" - content = (render / "README.md").read_text() - for tag in README_REQUIRED_TAGS: - assert tag in content, f"missing tag: {tag}" - assert "# 🚀 " in content - assert "## 🇺🇸 English" in content - assert "## 🇷🇺 Русский" in content - assert "[English](#-english)" in content - assert "[Русский](#-русский)" in content - assert "assets/cover.png" in content - - # ── pyproject.toml completeness ────────────────────────────────────────────── @@ -462,6 +419,9 @@ def test_pyproject_has_required_sections(render): ] for section in required: assert section in content, f"missing section: {section}" + assert 'readme = "README.md"' not in content, ( + "README.md is absent (issue #269) — readme field must not reference it" + ) @pytest.mark.parametrize("template_name, extra_context", [("backend", {"project_name": "be"})]) @@ -471,6 +431,29 @@ def test_backend_pytest_asyncio_auto(render): assert 'asyncio_mode = "auto"' in pyproject +@pytest.mark.parametrize( + "template_name, extra_context", + [ + ("backend", {"project_name": "be"}), + ("cli", {"project_name": "cl"}), + ("fullstack", {"project_name": "fs", "use_db": "yes"}), + ], +) +def test_pyproject_no_readme_field(render, template_name): + """pyproject must not reference README.md (issue #269 — README is absent). + + hatchling fails the build when ``readme = "README.md"`` points to a + missing file, so the field must be dropped entirely. + """ + if template_name == "fullstack": + content = (render / "backend" / "pyproject.toml").read_text() + else: + content = (render / "pyproject.toml").read_text() + assert 'readme = "README.md"' not in content, ( + "README.md is absent (issue #269) — readme field must not reference it" + ) + + # ── nested src// layout (issue #241) ────────────────────────────────── @@ -559,8 +542,13 @@ def _run_status_checks(repo_root: Path, fast: bool = True) -> tuple[str, list[ps "template_name, extra_context", [("backend", {"project_name": "be", "use_db": "yes", "use_auth": "yes"})], ) -def test_backend_project_status_quality_and_readme_pass(render): - """Backend template passes the quality + README + infra checks. +def test_backend_project_status_quality_and_infra_pass(render): + """Backend template passes the quality + infra checks. + + README is intentionally absent (issue #269 — generated later via + repo-readme/create-readme on manual call), so the README group yields + WARN, not FAIL (non-blocking). Coverage of the WARN behavior lives in + ``test_readme_absent_is_warn``. Note: the structure check (``src/api/v1`` flat layout) expects the layout from issue #2 — out of scope for this PR. The nested ``src//api/v1`` @@ -573,12 +561,6 @@ def test_backend_project_status_quality_and_readme_pass(render): assert by_name["ruff"].status == ps.CheckStatus.OK assert by_name["mypy"].status == ps.CheckStatus.OK assert by_name["pytest"].status == ps.CheckStatus.OK - # README delimiter tags + standard headers must pass - assert by_name["12 delimiter tags"].status == ps.CheckStatus.OK - assert by_name["# 🚀 "].status == ps.CheckStatus.OK - assert by_name["## 🇺🇸 English"].status == ps.CheckStatus.OK - assert by_name["## 🇷🇺 Русский"].status == ps.CheckStatus.OK - assert by_name["[English](#-english)"].status == ps.CheckStatus.OK # infra: ci.yml + LICENSE + pre-commit assert by_name[".github/workflows/ci.yml"].status == ps.CheckStatus.OK assert by_name["LICENSE"].status == ps.CheckStatus.OK @@ -605,11 +587,13 @@ def test_pyproject_quality_checks_pass(render): "template_name, extra_context", [("backend", {"project_name": "be"}), ("cli", {"project_name": "cl"})], ) -def test_readme_check_passes(render): - """README delimiter-tag check passes in the rendered project.""" +def test_readme_absent_is_warn(render): + """README.md is intentionally absent (issue #269) — project-status yields + WARN, not FAIL (non-blocking, exit 0).""" _, checks = _run_status_checks(render) by_name = {c.name: c for c in checks} - assert by_name["12 delimiter tags"].status == ps.CheckStatus.OK + assert by_name["README.md"].status == ps.CheckStatus.WARN + assert "12 delimiter tags" not in by_name @pytest.mark.parametrize(