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 <agent@opencode.local>
This commit is contained in:
Sergey 2026-08-05 04:15:43 +03:00 committed by GitHub
parent 70b8203704
commit ca2a04ce47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 55 additions and 260 deletions

View file

@ -112,6 +112,11 @@ Tool вернёт отчёт: `Project: <type>`, 7 групп `[OK]/[WARN]/[FAIL
`Рекомендации:`. Покажи отчёт юзеру. Если tool вернул `⚠️ ...failed` → WARN, `Рекомендации:`. Покажи отчёт юзеру. Если tool вернул `⚠️ ...failed` → WARN,
продолжай без отчёта. продолжай без отчёта.
> README у свежего проекта отсутствует (issue #269) — README генерируется
> позже через repo-readme skill по ручному вызову. Поэтому группа README
> даст WARN «README.md отсутствует» (не FAIL, exit code 0 в non-blocking
> режиме). Это ожидаемое поведение, не ошибка — упомяни в отчёте.
### Шаг 6: Финальный репорт ### Шаг 6: Финальный репорт
``` ```
@ -119,6 +124,7 @@ Project <project_name> created at ./<project_name>/.
GitHub: https://github.com/<owner>/<project_name> GitHub: https://github.com/<owner>/<project_name>
Branch protection: main (PR + required_status_checks + linear history) Branch protection: main (PR + required_status_checks + linear history)
Project-status: <summary из шага 5> Project-status: <summary из шага 5>
README: отсутствует (WARN у project-status) — появится позже через repo-readme
Дальше: /run-pipeline для реализации фич, или /spec для генерации spec. Дальше: /run-pipeline для реализации фич, или /spec для генерации spec.
``` ```
@ -206,6 +212,11 @@ Template FIX (ниже), передав путь и описание пробл
заблокирован deny). заблокирован deny).
7. Верни: "done: project created at ./<project_name>/, git init + initial commit". 7. Верни: "done: project created at ./<project_name>/, git init + initial commit".
README.md в рендеренном проекте НЕТ (issue #269) — шаблоны его больше не
содержат. README появится позже через repo-readme skill по ручному вызову
(сначала юзер смотрит контент и cover.png из draw-image). project-status на
свежем проекте даст WARN по отсутствующему README — это ожидаемо, не чинить.
Если найдёшь баг вне scope — загрузи skill `bug-discovery` через Если найдёшь баг вне scope — загрузи skill `bug-discovery` через
`skill("bug-discovery")` и следуй протоколу. НЕ чини баг сам. `skill("bug-discovery")` и следуй протоколу. НЕ чини баг сам.
``` ```
@ -331,7 +342,8 @@ Cookiecutter templates доступны для: `backend`, `fullstack`, `cli`
- `project-status` tool — read-only oracle, ALLOWED для оркестратора (как - `project-status` tool — read-only oracle, ALLOWED для оркестратора (как
`pipeline-status` / `spec-status`). `pipeline-status` / `spec-status`).
- init flow порядок: cookiecutter → git init/commit → gh repo create → branch - 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 (последовательно). - check flow: project-status → отчёт → рекомендации → subagents (последовательно).
- Subagent error → 1 retry, потом STOP + report. - Subagent error → 1 retry, потом STOP + report.
- Совместим с spec-pipeline: Phase 8 EXECUTE (spec/SKILL.md Template I) может - Совместим с spec-pipeline: Phase 8 EXECUTE (spec/SKILL.md Template I) может

View file

@ -201,9 +201,10 @@ switcher `[English]` / `[Русский]`, заголовок `## 🇷🇺 Ру
## 6. Независимость от project-template ## 6. Независимость от project-template
- Скилл `project-template` (init flow) создаёт проект через cookiecutter — - Скилл `project-template` (init flow) создаёт проект через cookiecutter —
шаблон уже включает `README.md` с базовой структурой. шаблоны README.md НЕ содержат (issue #269): свежий проект рождается без
- `repo-readme` (через тулзу `create-readme`) **наполняет** его README.
стандартизированным контентом (delimiter tags, bilingual, cover). - `repo-readme` (через тулзу `create-readme`) **создаёт** полный README с нуля
(delimiter tags, bilingual, cover) по ручному вызову после init.
- Может применяться к существующим репо без `project-template` — тулза - Может применяться к существующим репо без `project-template` — тулза
перезапишет `README.md` (локально) или обновит через GitHub API (с SHA). перезапишет `README.md` (локально) или обновит через GitHub API (с SHA).

View file

@ -1,67 +0,0 @@
# 🚀 {{ cookiecutter.project_name }}
> Language switcher: **[English](#-english)** | **[Русский](#-русский)**
![Cover](assets/cover.png)
<!-- tagline-en:start -->
{{ cookiecutter.description }}
<!-- tagline-en:end -->
<!-- summary-en:start -->
{{ cookiecutter.description }}
<!-- summary-en:end -->
## 🇺🇸 English
<!-- features-en:start -->
- FastAPI backend with Tortoise ORM
- Pydantic-settings configuration
- Ruff + mypy strict + pytest
- Pre-commit hooks
<!-- features-en:end -->
### ⚡ Quick Start
```bash
uv sync --extra dev
uv run uvicorn main:app --reload
```
---
## 💬 Support and contacts / Поддержка и контакты
👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)**
---
## 🇷🇺 Русский
<!-- tagline-ru:start -->
{{ cookiecutter.description }}
<!-- tagline-ru:end -->
<!-- summary-ru:start -->
{{ cookiecutter.description }}
<!-- summary-ru:end -->
<!-- features-ru:start -->
- Бэкенд на FastAPI с Tortoise ORM
- Конфигурация через pydantic-settings
- Ruff + mypy strict + pytest
- Pre-commit хуки
<!-- features-ru:end -->
### ⚡ Быстрый старт
```bash
uv sync --extra dev
uv run uvicorn main:app --reload
```
---
## 💬 Support and contacts / Поддержка и контакты
👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)**

View file

@ -6,7 +6,6 @@ build-backend = "hatchling.build"
name = "{{ cookiecutter.project_name }}" name = "{{ cookiecutter.project_name }}"
version = "0.1.0" version = "0.1.0"
description = "{{ cookiecutter.description }}" description = "{{ cookiecutter.description }}"
readme = "README.md"
license = "MIT" license = "MIT"
requires-python = ">={{ cookiecutter.python_version }}" requires-python = ">={{ cookiecutter.python_version }}"
authors = [{ name = "slaid098" }] authors = [{ name = "slaid098" }]

View file

@ -1,65 +0,0 @@
# 🚀 {{ cookiecutter.project_name }}
> Language switcher: **[English](#-english)** | **[Русский](#-русский)**
![Cover](assets/cover.png)
<!-- tagline-en:start -->
{{ cookiecutter.description }}
<!-- tagline-en:end -->
<!-- summary-en:start -->
{{ cookiecutter.description }}
<!-- summary-en:end -->
## 🇺🇸 English
<!-- features-en:start -->
- Typer-based CLI with rich output
- Ruff + mypy strict + pytest
- Pre-commit hooks
<!-- features-en:end -->
### ⚡ Quick Start
```bash
uv sync --extra dev
{{ cookiecutter.project_name }} --help
```
---
## 💬 Support and contacts / Поддержка и контакты
👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)**
---
## 🇷🇺 Русский
<!-- tagline-ru:start -->
{{ cookiecutter.description }}
<!-- tagline-ru:end -->
<!-- summary-ru:start -->
{{ cookiecutter.description }}
<!-- summary-ru:end -->
<!-- features-ru:start -->
- CLI на Typer с rich-выводом
- Ruff + mypy strict + pytest
- Pre-commit хуки
<!-- features-ru:end -->
### ⚡ Быстрый старт
```bash
uv sync --extra dev
{{ cookiecutter.project_name }} --help
```
---
## 💬 Support and contacts / Поддержка и контакты
👉 **[slaid098.dev/contacts](https://slaid098.dev/contacts)**

View file

@ -6,7 +6,6 @@ build-backend = "hatchling.build"
name = "{{ cookiecutter.project_name }}" name = "{{ cookiecutter.project_name }}"
version = "0.1.0" version = "0.1.0"
description = "{{ cookiecutter.description }}" description = "{{ cookiecutter.description }}"
readme = "README.md"
license = "MIT" license = "MIT"
requires-python = ">={{ cookiecutter.python_version }}" requires-python = ">={{ cookiecutter.python_version }}"
authors = [{ name = "slaid098" }] authors = [{ name = "slaid098" }]

View file

@ -1,67 +0,0 @@
# 🚀 {{ cookiecutter.project_name }}
> Language switcher: **[English](#-english)** | **[Русский](#-русский)**
![Cover](assets/cover.png)
<!-- tagline-en:start -->
{{ cookiecutter.description }}
<!-- tagline-en:end -->
<!-- summary-en:start -->
{{ cookiecutter.description }}
<!-- summary-en:end -->
## 🇺🇸 English
<!-- features-en:start -->
- 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)
<!-- features-en:end -->
### ⚡ 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)**
---
## 🇷🇺 Русский
<!-- tagline-ru:start -->
{{ cookiecutter.description }}
<!-- tagline-ru:end -->
<!-- summary-ru:start -->
{{ cookiecutter.description }}
<!-- summary-ru:end -->
<!-- features-ru:start -->
- Бэкенд 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 (фронтенд)
<!-- features-ru:end -->
### ⚡ Быстрый старт
```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)**

View file

@ -6,7 +6,6 @@ build-backend = "hatchling.build"
name = "{{ cookiecutter.project_name }}" name = "{{ cookiecutter.project_name }}"
version = "0.1.0" version = "0.1.0"
description = "{{ cookiecutter.description }}" description = "{{ cookiecutter.description }}"
readme = "README.md"
license = "MIT" license = "MIT"
requires-python = ">={{ cookiecutter.python_version }}" requires-python = ">={{ cookiecutter.python_version }}"
authors = [{ name = "slaid098" }] authors = [{ name = "slaid098" }]

View file

@ -104,7 +104,6 @@ def test_backend_structure_full(render):
".python-version", ".python-version",
"env.example", "env.example",
".gitignore", ".gitignore",
"README.md",
"LICENSE", "LICENSE",
".pre-commit-config.yaml", ".pre-commit-config.yaml",
".github/workflows/ci.yml", ".github/workflows/ci.yml",
@ -201,7 +200,6 @@ def test_cli_structure(render):
expected = [ expected = [
"pyproject.toml", "pyproject.toml",
".python-version", ".python-version",
"README.md",
"LICENSE", "LICENSE",
".github/workflows/ci.yml", ".github/workflows/ci.yml",
"src/cl/__init__.py", "src/cl/__init__.py",
@ -263,7 +261,6 @@ def test_fullstack_structure(render):
# removed: counter.svelte.js, Header.svelte, jsconfig.json, +page.js # removed: counter.svelte.js, Header.svelte, jsconfig.json, +page.js
# root CI runs both # root CI runs both
assert (render / ".github/workflows/ci.yml").exists() assert (render / ".github/workflows/ci.yml").exists()
assert (render / "README.md").exists()
@pytest.mark.parametrize( @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 assert "tw-animate" not in deps or "tw-animate-css" in deps
# ── README delimiter tags (create-readme standard) ──────────────────────────
README_REQUIRED_TAGS = [
"<!-- tagline-en:start -->",
"<!-- tagline-en:end -->",
"<!-- tagline-ru:start -->",
"<!-- tagline-ru:end -->",
"<!-- summary-en:start -->",
"<!-- summary-en:end -->",
"<!-- features-en:start -->",
"<!-- features-en:end -->",
"<!-- summary-ru:start -->",
"<!-- summary-ru:end -->",
"<!-- features-ru:start -->",
"<!-- features-ru:end -->",
]
@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 ────────────────────────────────────────────── # ── pyproject.toml completeness ──────────────────────────────────────────────
@ -462,6 +419,9 @@ def test_pyproject_has_required_sections(render):
] ]
for section in required: for section in required:
assert section in content, f"missing section: {section}" 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"})]) @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 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/<package>/ layout (issue #241) ────────────────────────────────── # ── nested src/<package>/ 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", "template_name, extra_context",
[("backend", {"project_name": "be", "use_db": "yes", "use_auth": "yes"})], [("backend", {"project_name": "be", "use_db": "yes", "use_auth": "yes"})],
) )
def test_backend_project_status_quality_and_readme_pass(render): def test_backend_project_status_quality_and_infra_pass(render):
"""Backend template passes the quality + README + infra checks. """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 Note: the structure check (``src/api/v1`` flat layout) expects the layout
from issue #2 — out of scope for this PR. The nested ``src/<pkg>/api/v1`` from issue #2 — out of scope for this PR. The nested ``src/<pkg>/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["ruff"].status == ps.CheckStatus.OK
assert by_name["mypy"].status == ps.CheckStatus.OK assert by_name["mypy"].status == ps.CheckStatus.OK
assert by_name["pytest"].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 # infra: ci.yml + LICENSE + pre-commit
assert by_name[".github/workflows/ci.yml"].status == ps.CheckStatus.OK assert by_name[".github/workflows/ci.yml"].status == ps.CheckStatus.OK
assert by_name["LICENSE"].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", "template_name, extra_context",
[("backend", {"project_name": "be"}), ("cli", {"project_name": "cl"})], [("backend", {"project_name": "be"}), ("cli", {"project_name": "cl"})],
) )
def test_readme_check_passes(render): def test_readme_absent_is_warn(render):
"""README delimiter-tag check passes in the rendered project.""" """README.md is intentionally absent (issue #269) — project-status yields
WARN, not FAIL (non-blocking, exit 0)."""
_, checks = _run_status_checks(render) _, checks = _run_status_checks(render)
by_name = {c.name: c for c in checks} 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( @pytest.mark.parametrize(