Awesome-claude init-repo

install
source · Clone the upstream repo
git clone https://github.com/Hedgehogues/awesome-claude
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Hedgehogues/awesome-claude "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/init-repo" ~/.claude/skills/hedgehogues-awesome-claude-init-repo && rm -rf "$T"
manifest: skills/init-repo/SKILL.md
source content

Задача

Принципы архитектуры: DESIGN.md

Инициализируй DDD-монорепу. $ARGUMENTS

  • $0
    — имя проекта (kebab-case). Если не задано — спроси.
  • $1
    — имя первого bounded context (snake_case). Если не задано — используй
    core
    .

Контекст (предвычислено)

!

pwd
!
ls -la 2>/dev/null

Подстановки

PlaceholderИсточникПример
<project-name>
$0
kebab-case
acme-crm
<project_name>
$0
snake_case
acme_crm
<ProjectName>
$0
PascalCase
AcmeCrm
<bc_name>
$1
или
core
leads
<aggregate>
<bc_name>
singular
lead
<Aggregate>
PascalCase singular
Lead
<aggregates>
plural
leads

1. Root

Makefile — delegation-only

Targets:

check
,
lint
,
test
,
test-all
,
up
,
down
,
migrate
,
revision
. Pattern:
$(MAKE) -C packages/<pkg> <target>
.

docker-compose.yml

  • postgres
    — PostgreSQL 16, volume
    pgdata
    , port 5432
  • back
    — build
    packages/back
    , port 8000, depends_on postgres
  • front
    — build
    packages/front
    , port 3000

.env.example

DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/<project_name>
OPENAI_API_KEY=test-key
ENVIRONMENT=local
CORS_ORIGINS=["http://localhost:3000"]

CLAUDE.md — описание проекта, tech stack, команды. Адаптируй под имя.

2. Backend (
packages/back/
)

Структура

packages/back/
├── Makefile
├── pyproject.toml
├── alembic.ini
├── migrations/env.py + versions/
├── src/
│   ├── __init__.py
│   ├── main.py              # FastAPI app + lifespan + structlog + CORS
│   ├── config.py            # Settings(BaseSettings) + engine + async_session
│   ├── dependencies.py      # Composition root — единственный файл с infra-импортами
│   ├── models.py            # Model registry: import Base + models → combined_metadata
│   ├── router.py            # APIRouter(prefix="/api"), include_router из каждого BC
│   └── <bc_name>/
│       ├── __init__.py
│       ├── domain/
│       │   ├── __init__.py
│       │   ├── <aggregate>/
│       │   │   ├── __init__.py
│       │   │   ├── entity.py
│       │   │   └── repository.py
│       │   └── exceptions.py
│       ├── application/
│       │   ├── __init__.py
│       │   ├── create_<aggregate>.py
│       │   ├── get_<aggregate>.py
│       │   └── list_<aggregates>.py
│       ├── infrastructure/
│       │   ├── __init__.py
│       │   └── persistence/
│       │       ├── __init__.py
│       │       ├── base.py
│       │       ├── models.py
│       │       └── <aggregate>_repo.py
│       └── presentation/
│           ├── __init__.py
│           └── api/
│               ├── __init__.py
│               ├── routes.py
│               └── schemas.py
└── tests/
    ├── conftest.py           # .env.test load + shared fixtures + mock repos
    ├── .env.test
    ├── unit/<bc_name>/
    │   ├── __init__.py
    │   ├── test_entity.py
    │   └── test_use_cases.py
    └── architecture/
        ├── __init__.py
        ├── conftest.py
        └── test_layer_boundaries.py

pyproject.toml

[project]
name = "<project-name>-back"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.115.0",
    "uvicorn[standard]>=0.34.0",
    "sqlalchemy[asyncio]>=2.0.36",
    "asyncpg>=0.30.0",
    "alembic>=1.14.0",
    "httpx>=0.28.0",
    "pydantic-settings>=2.7.0",
    "structlog>=24.4.0",
]

[dependency-groups]
dev = [
    "pytest>=8.3.0",
    "pytest-asyncio>=0.24.0",
    "pytest-cov>=6.0.0",
    "ruff>=0.8.0",
    "mypy>=1.13.0",
    "black>=24.0.0",
]

[tool.ruff]
target-version = "py312"
line-length = 120

[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP", "B", "A", "SIM"]

[tool.ruff.lint.isort]
known-first-party = ["src"]

[tool.mypy]
python_version = "3.12"
strict = true
plugins = ["pydantic.mypy"]

[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false

[tool.black]
line-length = 120
target-version = ["py312"]

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
markers = [
    "unit: Unit tests",
    "architecture: DDD architecture validation tests",
    "state: State matrix tests",
    "security: Security tests",
    "cases: Business scenario tests",
    "integration: Integration tests",
]

[tool.coverage.run]
source = ["src"]
omit = ["src/main.py"]

[tool.coverage.report]
show_missing = true
fail_under = 0

Makefile (back)

.PHONY: lint test test-unit architecture-check test-cov check migrate revision format

RUNNER ?= uv run

lint:
	$(RUNNER) ruff check --fix .
	$(RUNNER) black --check .
	$(RUNNER) mypy src/ tests/

format:
	$(RUNNER) ruff check --fix .
	$(RUNNER) black .

test-unit:
	$(RUNNER) pytest tests/unit -v

architecture-check:
	$(RUNNER) pytest tests/architecture -v

test-cov:
	$(RUNNER) pytest tests/unit tests/architecture -v --cov --cov-report=term-missing

test: test-unit architecture-check

check: lint test test-cov
	@echo "All checks passed!"

migrate:
	$(RUNNER) alembic upgrade head

revision:
	$(RUNNER) alembic revision --autogenerate -m "$(msg)"

Код: DDD-слои

entity.py

@dataclass
class <Aggregate>:
    <aggregate>_id: uuid.UUID
    name: str
    version: int
    created_at: datetime
    updated_at: datetime

    @classmethod
    def create(cls, *, name: str) -> <Aggregate>:
        now = datetime.now(UTC)
        return cls(<aggregate>_id=uuid.uuid4(), name=name, version=1, created_at=now, updated_at=now)

repository.py

class <Aggregate>Repository(ABC):
    @abstractmethod
    async def save(self, entity: <Aggregate>) -> None: ...
    @abstractmethod
    async def update(self, entity: <Aggregate>) -> None: ...
    @abstractmethod
    async def get_by_id(self, <aggregate>_id: uuid.UUID) -> <Aggregate> | None: ...
    @abstractmethod
    async def get_all(self) -> list[<Aggregate>]: ...

create_<aggregate>.py

class Create<Aggregate>UseCase:
    def __init__(self, <aggregate>_repo: <Aggregate>Repository) -> None:
        self._<aggregate>_repo = <aggregate>_repo

    async def execute(self, *, name: str) -> <Aggregate>:
        entity = <Aggregate>.create(name=name)
        await self._<aggregate>_repo.save(entity)
        return entity

persistence/models.py

class <Aggregate>Model(Base):
    __tablename__ = "<aggregates>"
    <aggregate>_id: Mapped[uuid.UUID] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(nullable=False)
    version: Mapped[int] = mapped_column(default=1)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))

<aggregate>_repo.py

class SqlAlchemy<Aggregate>Repository(<Aggregate>Repository):
    def __init__(self, session: AsyncSession) -> None:
        self._session = session

    async def save(self, entity: <Aggregate>) -> None:
        model = <Aggregate>Model(
            <aggregate>_id=entity.<aggregate>_id, name=entity.name,
            version=entity.version, created_at=entity.created_at, updated_at=entity.updated_at,
        )
        self._session.add(model)
        await self._session.flush()

    async def get_by_id(self, <aggregate>_id: uuid.UUID) -> <Aggregate> | None:
        result = await self._session.execute(
            select(<Aggregate>Model).where(<Aggregate>Model.<aggregate>_id == <aggregate>_id)
        )
        row = result.scalar_one_or_none()
        if row is None:
            return None
        return <Aggregate>(
            <aggregate>_id=row.<aggregate>_id, name=row.name,
            version=row.version, created_at=row.created_at, updated_at=row.updated_at,
        )

routes.py

router = APIRouter(prefix="/<aggregates>", tags=["<Aggregates>"])

@router.post("")
async def create_<aggregate>(request: Create<Aggregate>Request) -> <Aggregate>Response:
    async with async_session() as db, db.begin():
        repo = SqlAlchemy<Aggregate>Repository(db)
        uc = Create<Aggregate>UseCase(<aggregate>_repo=repo)
        entity = await uc.execute(name=request.name)
    return _to_response(entity)

Тесты (back)

tests/architecture/ — DDD structural validation

conftest.py
: AST-helpers —
parse_imports()
,
parse_classes()
,
discover_aggregates()
(glob
*/entity.py
),
discover_domain_files()
,
discover_layer_files()
. Dataclasses:
ImportInfo
,
AggregateInfo
.

test_layer_boundaries.py
— parametrize по файлам каждого слоя:

  • Domain NOT imports application, infrastructure, presentation
  • Application NOT imports presentation
  • Presentation NOT imports infrastructure (use
    src.dependencies
    )
  • Domain/Application NOT imports sqlalchemy, fastapi, httpx, openai

tests/unit/<bc>/test_entity.py

pytestmark = pytest.mark.unit

def test_create_<aggregate>():
    """<Aggregate>.create produces entity with correct initial state."""
    entity = <Aggregate>.create(name="test")
    assert isinstance(entity.<aggregate>_id, uuid.UUID)
    assert entity.name == "test"
    assert entity.version == 1

tests/unit/<bc>/test_use_cases.py

pytestmark = [pytest.mark.unit, pytest.mark.asyncio]

async def test_create_<aggregate>_flow():
    """Create<Aggregate> persists new entity via repository."""
    repo = AsyncMock()
    repo.save.return_value = None
    uc = Create<Aggregate>UseCase(<aggregate>_repo=repo)
    result = await uc.execute(name="test")
    assert result.name == "test"
    repo.save.assert_awaited_once()

3. Frontend (
packages/front/
)

Структура

packages/front/
├── Makefile                 # install, lint (eslint + tsc), test, build, check, dev
├── package.json             # React 19, Router v7, Vite, Vitest, Testing Library, TS strict
├── vite.config.ts           # react plugin + vitest (jsdom, globals, setup)
├── tsconfig.json            # references → app + node
├── tsconfig.app.json        # ES2022, strict, react-jsx, vitest/globals
├── tsconfig.node.json
├── eslint.config.js         # flat config: js + tseslint + react-hooks + react-refresh
├── index.html               # <div id="root">, Google Fonts Inter
├── Dockerfile               # multi-stage: npm build → nginx
├── nginx.conf               # SPA fallback, /api proxy
└── src/
    ├── main.tsx             # StrictMode + createRoot
    ├── App.tsx              # BrowserRouter + Routes + AppLayout
    ├── index.css
    ├── api/client.ts        # BASE_URL from VITE_API_URL, ApiError, fetchJson/postJson/patchJson/putJson/deleteJson
    ├── types/api.ts         # API response types
    ├── components/
    │   ├── ui/
    │   │   ├── Button/Button.tsx + Button.module.css + Button.test.tsx
    │   │   └── index.ts     # barrel export
    │   ├── domain/          # empty
    │   └── layout/AppLayout.tsx + AppLayout.test.tsx
    ├── pages/HomePage.tsx + HomePage.test.tsx
    ├── hooks/               # empty
    ├── styles/base.css + layout.css
    └── test/
        ├── setup.ts         # import '@testing-library/jest-dom/vitest'
        ├── renderWithRouter.tsx
        └── fixtures.ts

vite.config.ts

/// <reference types="vitest/config" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/test/setup.ts',
    css: true,
    exclude: ['node_modules', 'e2e'],
  },
})

Makefile (front)

.PHONY: install lint test build check dev

install:
	npm install

lint:
	npm run lint
	npx tsc -b --noEmit

test:
	npm run test

build:
	npm run build

check: lint test build
	@echo "All checks passed!"

dev:
	npm run dev

4. После генерации

Создай root → back (config → src по слоям → tests) → front (config → src → tests). Выведи:

Монорепа <project-name> создана.

  cd <project-name>
  git init && git add -A && git commit -m "init: scaffold <project-name> monorepo"

  cd packages/back && uv sync && make check
  cd packages/front && npm install && make check
  make up

Правила

  • Все файлы рабочие —
    make check
    проходит с первого раза
  • Только каркас (CRUD entity, empty pages), без бизнес-логики
  • Только базовый стек: FastAPI + SQLAlchemy + React. Без OpenAI, Celery, Redis
  • Подставляй имена из аргументов во все файлы (imports, classes, tables)
  • __init__.py
    в каждой Python-директории
  • Architecture tests, unit tests, frontend tests — все проходят