diff --git a/.opencode/scripts/project_contract.py b/.opencode/scripts/project_contract.py index 7de965f..c8a19b2 100644 --- a/.opencode/scripts/project_contract.py +++ b/.opencode/scripts/project_contract.py @@ -51,6 +51,7 @@ STACK_REQUIRED: dict[str, list[str]] = { "tailwind", "shadcn", "typescript", + "mobile-first", ], "mcp-server": ["fastapi", "mcp", "patchright", "uv"], "cli": ["typer", "uv", "hatchling", "ruff", "mypy", "pytest"], diff --git a/.opencode/skills/audit/SKILL.md b/.opencode/skills/audit/SKILL.md index cc83e21..d874431 100644 --- a/.opencode/skills/audit/SKILL.md +++ b/.opencode/skills/audit/SKILL.md @@ -96,6 +96,7 @@ REVIEW → MERGE). - service-слой пропущен (routes → db/models без services/) - файлы длиннее 200-300 строк (декомпозиция) - mixing concerns (бизнес-логика ≠ транспорт ≠ представление) +- mobile-first missing (fullstack): нет PWA manifest, нет Playwright mobile spec, нет axe a11y spec, нет viewport meta — `STACK_REQUIRED["fullstack"]` требует "mobile-first", но качественно проверь что mobile-first реален, а не просто слово в stack.md Для каждой находки верни: {category: "Code-standards", problem: ": ", path: "", severity: "warn"|"fail"} diff --git a/.opencode/skills/code-standards/SKILL.md b/.opencode/skills/code-standards/SKILL.md index e395fba..0b998ec 100644 --- a/.opencode/skills/code-standards/SKILL.md +++ b/.opencode/skills/code-standards/SKILL.md @@ -104,7 +104,7 @@ src// ### Fullstack (кратко) -Backend as above (in `backend/` + `frontend/` separation). Frontend: SvelteKit co-located `*.test.ts` в `src/lib/`, `e2e/*.spec.ts` для Playwright. НЕ смешивать backend код в `frontend/` и наоборот. +Backend as above (in `backend/` + `frontend/` separation). Frontend: SvelteKit co-located `*.test.ts` в `src/lib/`, `e2e/*.spec.ts` для Playwright. НЕ смешивать backend код в `frontend/` и наоборот. + mobile-first (PWA + Playwright mobile + axe a11y) — silent enforcement через `STACK_REQUIRED["fullstack"]`. ### CLI (кратко) diff --git a/.opencode/skills/spec/SKILL.md b/.opencode/skills/spec/SKILL.md index e45478b..681ff82 100644 --- a/.opencode/skills/spec/SKILL.md +++ b/.opencode/skills/spec/SKILL.md @@ -42,7 +42,7 @@ truth для порядка и действий — `spec-status` tool. На в Общий для всех типов: Python 3.12+, uv, hatchling, ruff, mypy strict, pytest 90% cov, xenon, pre-commit, .editorconfig, .gitignore, LICENSE MIT, dependabot, CI. - **backend**: FastAPI + uvicorn, Tortoise ORM (встроенные миграции `tortoise makemigrations`, НЕ Aerich — legacy), Pydantic v2 + pydantic-settings, Loguru, опц. JWT-auth (`passlib[bcrypt]` + `pyjwt`) -- **fullstack**: backend + frontend/ (SvelteKit + Svelte 5 runes (TS) + Tailwind v4 + shadcn-svelte + Biome + Vitest + Knip) +- **fullstack**: backend + frontend/ (SvelteKit + Svelte 5 runes (TS) + Tailwind v4 + shadcn-svelte + Biome + Vitest + Knip + mobile-first (PWA + axe + Playwright mobile)) - **mcp-server**: FastAPI + MCP SDK, Patchright/Playwright over CDP, X-API-Key - **cli**: Typer (default) / click / argparse, hatchling build - **bot**: aiogram 3.x, FastAPI webhook/polling, Tortoise (опц.), Pydantic AI (опц.) @@ -88,7 +88,7 @@ backend: - Auth: [1] none v1 / [2] JWT / [3] X-API-Key fullstack: -- frontend: [1] SvelteKit + Svelte 5 + Tailwind v4 + shadcn-svelte (default) / [2] add later +- frontend: [1] SvelteKit + Svelte 5 + Tailwind v4 + shadcn-svelte (default, mobile-first: PWA + axe + Playwright mobile — silent) / [2] add later - DB: (same as backend) - Auth: (same as backend) @@ -236,7 +236,7 @@ Spec complete. Issues: #N1, #N2, ... Default stack для типа (хардкод, добавить всегда): - Общий: Python 3.12+, uv, hatchling, ruff, mypy strict, pytest 90% cov, xenon, pre-commit, .editorconfig, .gitignore, LICENSE MIT, dependabot, CI - backend: FastAPI + uvicorn, Tortoise ORM (встроенные миграции `tortoise makemigrations`, НЕ Aerich), Pydantic v2 + pydantic-settings, Loguru, опц. JWT-auth (`passlib[bcrypt]` + `pyjwt`) -- fullstack: + frontend/ (SvelteKit + Svelte 5 runes (TS) + Tailwind v4 + shadcn-svelte + Biome + Vitest + Knip) +- fullstack: + frontend/ (SvelteKit + Svelte 5 runes (TS) + Tailwind v4 + shadcn-svelte + Biome + Vitest + Knip + mobile-first (PWA + axe + Playwright mobile)) - mcp-server: FastAPI + MCP SDK, Patchright/Playwright over CDP, X-API-Key - cli: Typer (default) / click / argparse, hatchling build - bot: aiogram 3.x, FastAPI webhook/polling, Tortoise (опц.), Pydantic AI (опц.) diff --git a/.opencode/templates/fullstack/{{cookiecutter.project_name}}/.github/workflows/ci.yml b/.opencode/templates/fullstack/{{cookiecutter.project_name}}/.github/workflows/ci.yml index 688653a..815a277 100644 --- a/.opencode/templates/fullstack/{{cookiecutter.project_name}}/.github/workflows/ci.yml +++ b/.opencode/templates/fullstack/{{cookiecutter.project_name}}/.github/workflows/ci.yml @@ -46,9 +46,21 @@ jobs: - run: npm run lint - run: npm test - build: + frontend-e2e: runs-on: ubuntu-latest - needs: [backend-lint, backend-typecheck, backend-test, frontend-test] + defaults: { run: { working-directory: frontend } } steps: - uses: actions/checkout@v4 - - run: echo "All checks passed" \ No newline at end of file + - uses: actions/setup-node@v4 + with: { node-version: '20' } + - run: npm install + - run: npx playwright install --with-deps + - run: npm run build + - run: npm run test:e2e + + build: + runs-on: ubuntu-latest + needs: [backend-lint, backend-typecheck, backend-test, frontend-test, frontend-e2e] + steps: + - uses: actions/checkout@v4 + - run: echo "All checks passed" diff --git a/.opencode/tools/project-status.ts b/.opencode/tools/project-status.ts index d5ae0b3..33e7c77 100644 --- a/.opencode/tools/project-status.ts +++ b/.opencode/tools/project-status.ts @@ -4,7 +4,7 @@ import { tool } from "@opencode-ai/plugin" export default tool({ description: - "Project status oracle. Read-only check of repo architecture conformance. Auto-detects project type (frontend→fullstack, fastapi→backend, typer→cli, aiogram→bot, prefect→worker) and runs 8 check groups: Структура, Тонкие роуты (AST ≤50 lines), Качество кода (mypy/ruff/pytest), Тесты (conftest, stub-detector, no @pytest.mark.asyncio), README (12 delimiter tags), Infra (branch protection, ci.yml, dependabot, LICENSE, pre-commit), Coverage (non-blocking), Pyproject (13 checks: build-system, hatch wheel, project fields, ruff/mypy/pytest config, coverage, pre-commit, uv.lock, requires-python vs .python-version). Issue #275: all checks are non-blocking (WARN) and the exit code is always 0 (informational mode); check=true is accepted for CLI compatibility but no longer forces exit 1; pass fast=true to skip slow/remote checks (branch protection); pass repo= to check an arbitrary repo instead of the current worktree.", + "Project status oracle. Read-only check of repo architecture conformance. Auto-detects project type (frontend→fullstack, fastapi→backend, typer→cli, aiogram→bot, prefect→worker) and runs 8 check groups: Структура (incl. mobile-first PWA + Playwright mobile + axe a11y for fullstack), Тонкие роуты (AST ≤50 lines), Качество кода (mypy/ruff/pytest), Тесты (conftest, stub-detector, no @pytest.mark.asyncio), README (12 delimiter tags), Infra (branch protection, ci.yml, dependabot, LICENSE, pre-commit), Coverage (non-blocking), Pyproject (13 checks: build-system, hatch wheel, project fields, ruff/mypy/pytest config, coverage, pre-commit, uv.lock, requires-python vs .python-version). Issue #275: all checks are non-blocking (WARN) and the exit code is always 0 (informational mode); check=true is accepted for CLI compatibility but no longer forces exit 1; pass fast=true to skip slow/remote checks (branch protection); pass repo= to check an arbitrary repo instead of the current worktree.", args: { check: tool.schema.boolean().optional().describe("Accepted for CLI compatibility — issue #275: exit code is always 0 (all checks WARN, non-blocking)"), fast: tool.schema.boolean().optional().describe("If true, skip slow/remote checks (branch protection via gh)"), diff --git a/tests/test_cookiecutter_templates.py b/tests/test_cookiecutter_templates.py index 1626aac..26d5054 100644 --- a/tests/test_cookiecutter_templates.py +++ b/tests/test_cookiecutter_templates.py @@ -402,6 +402,30 @@ def test_fullstack_uses_typescript(render): assert "tw-animate" not in deps or "tw-animate-css" in deps +@pytest.mark.parametrize("template_name, extra_context", [("fullstack", {"project_name": "fs"})]) +def test_fullstack_ci_has_frontend_e2e_job(render): + """Root ci.yml has a ``frontend-e2e`` job that installs deps without a + lockfile (``npm install``, NOT ``npm ci``), wires Playwright, and runs + in the ``frontend/`` working directory. + + Regression guard for PR#281 review critical #1: cookiecutter templates + ship no ``package-lock.json``, so ``npm ci`` fails in fresh projects. + """ + ci = (render / ".github/workflows/ci.yml").read_text() + assert "frontend-e2e" in ci, "ci.yml must define a frontend-e2e job" + assert "npm install" in ci, "ci.yml must use npm install (no lockfile in template)" + assert "npm ci" not in ci, ( + "ci.yml must NOT use npm ci (cookiecutter template has no package-lock.json)" + ) + assert "playwright install --with-deps" in ci, ( + "ci.yml must install Playwright browsers with --with-deps" + ) + assert "test:e2e" in ci, "ci.yml must run the e2e suite (npm run test:e2e)" + assert "working-directory: frontend" in ci, ( + "ci.yml must run frontend steps in the frontend/ working directory" + ) + + # ── pyproject.toml completeness ──────────────────────────────────────────────