feat(spec): mobile-first silent enforcement in STACK_REQUIRED + CI e2e (#281)
Some checks failed
CI / bootstrap (push) Successful in 52s
CI / lint (push) Successful in 2m2s
CI / typecheck (push) Successful in 27s
CI / test (3.12) (push) Failing after 2m53s
CI / test (3.13) (push) Failing after 2m0s
CI / test (3.14) (push) Failing after 1m48s
CI / complexity (push) Successful in 23s

* feat(spec): mobile-first silent enforcement in STACK_REQUIRED + Template C

* feat(ci): frontend-e2e job with Playwright in fullstack cookiecutter

* docs(skills): mention mobile-first in audit, code-standards, project-status tool

* fix(ci): use npm install instead of npm ci + add e2e regression test

---------

Co-authored-by: opencode-agent <agent@opencode.local>
This commit is contained in:
Sergey 2026-08-05 07:05:14 +03:00 committed by GitHub
parent 6bb115e706
commit 9459e47567
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 46 additions and 8 deletions

View file

@ -51,6 +51,7 @@ STACK_REQUIRED: dict[str, list[str]] = {
"tailwind", "tailwind",
"shadcn", "shadcn",
"typescript", "typescript",
"mobile-first",
], ],
"mcp-server": ["fastapi", "mcp", "patchright", "uv"], "mcp-server": ["fastapi", "mcp", "patchright", "uv"],
"cli": ["typer", "uv", "hatchling", "ruff", "mypy", "pytest"], "cli": ["typer", "uv", "hatchling", "ruff", "mypy", "pytest"],

View file

@ -96,6 +96,7 @@ REVIEW → MERGE).
- service-слой пропущен (routes → db/models без services/) - service-слой пропущен (routes → db/models без services/)
- файлы длиннее 200-300 строк (декомпозиция) - файлы длиннее 200-300 строк (декомпозиция)
- mixing concerns (бизнес-логика ≠ транспорт ≠ представление) - 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: "<name>: <detail>", path: "<file:line>", severity: "warn"|"fail"} {category: "Code-standards", problem: "<name>: <detail>", path: "<file:line>", severity: "warn"|"fail"}

View file

@ -104,7 +104,7 @@ src/<package>/
### Fullstack (кратко) ### 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 (кратко) ### CLI (кратко)

View file

@ -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. Общий для всех типов: 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`) - **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 - **mcp-server**: FastAPI + MCP SDK, Patchright/Playwright over CDP, X-API-Key
- **cli**: Typer (default) / click / argparse, hatchling build - **cli**: Typer (default) / click / argparse, hatchling build
- **bot**: aiogram 3.x, FastAPI webhook/polling, Tortoise (опц.), Pydantic AI (опц.) - **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 - Auth: [1] none v1 / [2] JWT / [3] X-API-Key
fullstack: 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) - DB: (same as backend)
- Auth: (same as backend) - Auth: (same as backend)
@ -236,7 +236,7 @@ Spec complete. Issues: #N1, #N2, ...
Default stack для типа (хардкод, добавить всегда): Default stack для типа (хардкод, добавить всегда):
- Общий: Python 3.12+, uv, hatchling, ruff, mypy strict, pytest 90% cov, xenon, pre-commit, .editorconfig, .gitignore, LICENSE MIT, dependabot, CI - Общий: 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`) - 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 - mcp-server: FastAPI + MCP SDK, Patchright/Playwright over CDP, X-API-Key
- cli: Typer (default) / click / argparse, hatchling build - cli: Typer (default) / click / argparse, hatchling build
- bot: aiogram 3.x, FastAPI webhook/polling, Tortoise (опц.), Pydantic AI (опц.) - bot: aiogram 3.x, FastAPI webhook/polling, Tortoise (опц.), Pydantic AI (опц.)

View file

@ -46,9 +46,21 @@ jobs:
- run: npm run lint - run: npm run lint
- run: npm test - run: npm test
build: frontend-e2e:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [backend-lint, backend-typecheck, backend-test, frontend-test] defaults: { run: { working-directory: frontend } }
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: echo "All checks passed" - 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"

View file

@ -4,7 +4,7 @@ import { tool } from "@opencode-ai/plugin"
export default tool({ export default tool({
description: 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=<path> 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=<path> to check an arbitrary repo instead of the current worktree.",
args: { args: {
check: tool.schema.boolean().optional().describe("Accepted for CLI compatibility — issue #275: exit code is always 0 (all checks WARN, non-blocking)"), 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)"), fast: tool.schema.boolean().optional().describe("If true, skip slow/remote checks (branch protection via gh)"),

View file

@ -402,6 +402,30 @@ 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
@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 ────────────────────────────────────────────── # ── pyproject.toml completeness ──────────────────────────────────────────────