feat(spec): update stack required and jwt auth template (#236)

* feat(spec): update STACK_REQUIRED for svelte and quality tools

* fix(templates): align jwt auth with issue contracts

* docs(spec): update template c stack and migration note

* fix(ci): format tests/test_spec_status.py

---------

Co-authored-by: opencode-agent <agent@opencode.local>
This commit is contained in:
Sergey 2026-08-03 17:21:43 +03:00 committed by GitHub
parent 35b6c6e542
commit 51a5300423
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 36 additions and 22 deletions

View file

@ -70,12 +70,22 @@ PHASE_FILES: dict[int, str] = {
VALID_TYPES = {"backend", "fullstack", "mcp-server", "cli", "bot", "worker"}
STACK_REQUIRED: dict[str, list[str]] = {
"backend": ["fastapi", "tortoise", "uv", "pytest", "ruff", "mypy"],
"fullstack": ["fastapi", "tortoise", "react", "vite", "biome", "uv"],
"backend": ["fastapi", "tortoise", "uv", "pytest", "ruff", "mypy", "loguru", "pydantic"],
"fullstack": [
"fastapi",
"tortoise",
"svelte",
"sveltekit",
"biome",
"uv",
"ruff",
"mypy",
"pytest",
],
"mcp-server": ["fastapi", "mcp", "patchright", "uv"],
"cli": ["typer", "uv", "hatchling"],
"bot": ["aiogram", "fastapi", "uv"],
"worker": ["prefect", "uv"],
"cli": ["typer", "uv", "hatchling", "ruff", "mypy", "pytest"],
"bot": ["aiogram", "fastapi", "uv", "ruff", "mypy", "pytest"],
"worker": ["prefect", "uv", "ruff", "mypy", "pytest"],
}
PHASE_NAMES = [

View file

@ -41,13 +41,17 @@ 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 + Aerich, pydantic-settings, Loguru
- **fullstack**: backend + frontend/ (React 19 + Vite + Biome + TS strict + Vitest + Knip + happy-dom)
- **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 (JS) + Biome + Vitest + Knip)
- **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 (опц.)
- **worker**: Prefect flows + tasks, prefect.yaml, docker-compose worker profile
### Миграционная заметка (stack.md)
Если существующий `docs/spec/stack.md` содержит `react` или `aerich` (устаревшие значения до issue #231), spec-status пометит Phase 2 как NOT_DONE (`STACK_REQUIRED` теперь требует `svelte`/`sveltekit` для fullstack и не требует `aerich`). Обнови stack.md: замени `react``svelte`/`sveltekit`, удали `aerich` (Tortoise 1.0+ имеет встроенные миграции `tortoise makemigrations`).
## 9 фаз
### Phase 0: DETECT (subagent, без вопроса юзеру)
@ -61,7 +65,7 @@ Prompt template A (см. ниже).
```
Выбери тип проекта:
[1] backend — FastAPI + Tortoise, REST API, без frontend
[2] fullstack — backend + React 19/Vite dashboard (monorepo)
[2] fullstack — backend + SvelteKit/Svelte 5 dashboard (monorepo)
[3] mcp-server — MCP + REST сервер (Patchright/Playwright over CDP)
[4] cli — Python CLI tool (Typer)
[5] bot — Telegram bot (aiogram 3)
@ -84,7 +88,7 @@ backend:
- Auth: [1] none v1 / [2] JWT / [3] X-API-Key
fullstack:
- frontend: [1] React 19 (default) / [2] SvelteKit / [3] add later
- frontend: [1] SvelteKit + Svelte 5 (default) / [2] add later
- DB: (same as backend)
- Auth: (same as backend)
@ -231,8 +235,8 @@ Spec complete. Issues: #N1, #N2, ...
Тип проекта: <type> (из frontmatter meta.md).
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 + Aerich, pydantic-settings, Loguru
- fullstack: + frontend/ (React 19 + Vite + Biome + TS strict + Vitest + Knip + happy-dom)
- backend: FastAPI + uvicorn, Tortoise ORM (встроенные миграции `tortoise makemigrations`, НЕ Aerich), Pydantic v2 + pydantic-settings, Loguru, опц. JWT-auth (`passlib[bcrypt]` + `pyjwt`)
- fullstack: + frontend/ (SvelteKit + Svelte 5 runes (JS) + Biome + Vitest + Knip)
- 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 (опц.)

View file

@ -45,5 +45,5 @@ async def get_current_user(
Returns the username/subject of the token. Only present when
``use_auth == "yes"``.
"""
return await auth.get_current_user(credentials.credentials)
return await auth.decode_access_token(credentials.credentials)
{% endif %}

View file

@ -15,7 +15,7 @@ class User(Model):
username = fields.CharField(max_length=64, unique=True)
email = fields.CharField(max_length=128, unique=True)
{% if cookiecutter.use_auth == "yes" %}
hashed_password = fields.CharField(max_length=128)
hashed_password = fields.CharField(max_length=255)
{% endif %}
class Meta:

View file

@ -30,7 +30,7 @@ class AuthService:
return _pwd.verify(plain, hashed)
@staticmethod
def create_token(username: str) -> str:
def create_access_token(username: str) -> str:
expire = datetime.now(timezone.utc) + timedelta(
minutes=settings.jwt.expire_minutes,
)
@ -52,10 +52,10 @@ class AuthService:
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
)
return AuthService.create_token(username)
return AuthService.create_access_token(username)
@staticmethod
async def get_current_user(token: str) -> str:
async def decode_access_token(token: str) -> str:
try:
payload = jwt.decode(
token, settings.jwt.secret, algorithms=[settings.jwt.algorithm],

View file

@ -45,5 +45,5 @@ async def get_current_user(
Returns the username/subject of the token. Only present when
``use_auth == "yes"``.
"""
return await auth.get_current_user(credentials.credentials)
return await auth.decode_access_token(credentials.credentials)
{% endif %}

View file

@ -15,7 +15,7 @@ class User(Model):
username = fields.CharField(max_length=64, unique=True)
email = fields.CharField(max_length=128, unique=True)
{% if cookiecutter.use_auth == "yes" %}
hashed_password = fields.CharField(max_length=128)
hashed_password = fields.CharField(max_length=255)
{% endif %}
class Meta:

View file

@ -30,7 +30,7 @@ class AuthService:
return _pwd.verify(plain, hashed)
@staticmethod
def create_token(username: str) -> str:
def create_access_token(username: str) -> str:
expire = datetime.now(timezone.utc) + timedelta(
minutes=settings.jwt.expire_minutes,
)
@ -52,10 +52,10 @@ class AuthService:
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
)
return AuthService.create_token(username)
return AuthService.create_access_token(username)
@staticmethod
async def get_current_user(token: str) -> str:
async def decode_access_token(token: str) -> str:
try:
payload = jwt.decode(
token, settings.jwt.secret, algorithms=[settings.jwt.algorithm],

View file

@ -515,7 +515,7 @@ def test_check_stack_case_insensitive(monkeypatch, tmp_path):
spec_dir = _set_spec_dir(monkeypatch, tmp_path)
_write_meta(spec_dir, project="foo", type="backend")
(spec_dir / "stack.md").write_text(
"- FastAPI\n- Tortoise ORM\n- UV\n- Pytest\n- Ruff\n- Mypy\n"
"- FastAPI\n- Tortoise ORM\n- UV\n- Pytest\n- Ruff\n- Mypy\n- Loguru\n- Pydantic\n"
)
result = ss.check_stack()
assert result.status == ss.PhaseStatus.DONE