Gratis downloads
Starterskit
Alles om de Architecture First patronen in je project op te zetten. Download, drop in je repo, pas aan. Binnen 90 minuten van nul naar werkende setup.
.claude/ templates
De 5 documentatiebestanden uit les 3.
Zet ze in een .claude/ directory in je project root.
# Architecture
## Stack
| Layer | Choice | Why |
|------------|--------------------|--------------------------------------------------|
| Backend | FastAPI (Python 3.11+) | Async-first, auto-generated OpenAPI docs |
| Frontend | React 18 + TypeScript | Type safety across the stack, large ecosystem |
| Database | PostgreSQL 15+ | JSON fields, RLS for multi-tenancy, battle-tested |
| Cache | Redis 7+ | Sessions, rate limiting, pub/sub |
| Auth | JWT (access + refresh) | Stateless, works with mobile and SPA |
| ORM | SQLAlchemy 2.0 (async) | Mature, supports advanced PostgreSQL features |
| Validation | Pydantic V2 | Shared models between API and internal logic |
## Key Decisions
1. **Monorepo, not microservices.** We split by domain inside one repo. Microservices come later when a domain needs independent scaling.
2. **API-first.** OpenAPI spec is generated from code. Frontend types are generated from the spec. No hand-written API clients.
3. **Enums live in migrations.** Never auto-created by the ORM. See `DATABASE_PATTERNS.md`.
4. **Events over direct calls between domains.** Domains publish events; other domains subscribe. No circular imports.
5. **RLS for tenant isolation.** PostgreSQL Row-Level Security policies enforce data boundaries at the database level, not just in application code.
## Conventions
### Naming
- Files and directories: `snake_case`
- Python classes: `PascalCase`
- React components: `PascalCase` files and exports
- Database tables: `plural_snake_case` (e.g., `appointments`, `user_sessions`)
- API routes: `kebab-case` in URLs, `snake_case` in JSON bodies
### File Structure
```
backend/
app/
domains/{name}/ # One folder per domain
shared/ # Cross-cutting concerns (auth, events, middleware)
core/ # Config, database setup, base classes
frontend/
src/
features/{name}/ # One folder per feature/domain
shared/ # Reusable components, hooks, utils
```
### Error Handling
- All domain errors inherit from a base `AppError` class
- Errors carry a machine-readable `code` and a human-readable `message`
- Never expose stack traces in production responses
- Log the full error server-side; return a safe summary to the client
### Environment
- All secrets in `.env` (never committed)
- Config validated at startup via Pydantic Settings
- Fail fast: if a required config value is missing, the app does not start
# Domain Patterns
Every feature lives in its own domain folder. Domains are self-contained: they own their models, schemas, business logic, routes, and tests.
## Standard Domain Structure
```
app/domains/scheduling/
__init__.py
models.py # SQLAlchemy models. Database representation only.
schemas.py # Pydantic schemas. Input validation + API responses.
service.py # Business logic. The only place rules live.
router.py # FastAPI routes. Thin layer: validate, call service, return.
events.py # Events this domain publishes or subscribes to.
exceptions.py # Domain-specific error classes.
constants.py # Enums, magic values, config specific to this domain.
tests/
test_service.py # Unit tests for business logic.
test_router.py # Integration tests for API endpoints.
conftest.py # Fixtures scoped to this domain.
```
## What Goes Where
| File | Contains | Does NOT contain |
|----------------|----------------------------------------|-------------------------------------|
| `models.py` | Table definitions, relationships | Business logic, validation rules |
| `schemas.py` | Request/response shapes, field rules | Database queries, side effects |
| `service.py` | All business rules, orchestration | HTTP concerns, direct SQL |
| `router.py` | Route definitions, dependency injection | Business logic, raw queries |
| `events.py` | Event classes, publish/subscribe wiring | Request handling, database models |
| `exceptions.py`| Custom errors with codes and messages | Generic Python exceptions |
## Import Rules
These rules prevent circular dependencies and keep domains independent.
1. **Never import directly from another domain's internals.** No `from app.domains.billing.service import BillingService` inside the scheduling domain.
2. **Cross-domain communication uses events.** Scheduling publishes `AppointmentCreated`; billing subscribes and handles invoicing.
3. **Shared interfaces live in `app/shared/`.** If two domains need the same type, put it in shared.
4. **Router imports service. Service imports models and schemas. Never the reverse.**
```
router.py --> service.py --> models.py
--> schemas.py
--> events.py (publish)
```
## Example: Scheduling Domain
```python
# app/domains/scheduling/service.py
class SchedulingService:
def __init__(self, db: AsyncSession):
self.db = db
async def book_appointment(self, data: AppointmentCreate) -> Appointment:
slot = await self._get_available_slot(data.provider_id, data.start_time)
if not slot:
raise SlotUnavailableError(data.start_time)
appointment = Appointment(**data.model_dump(), status="confirmed")
self.db.add(appointment)
await self.db.flush()
await publish(AppointmentBooked(appointment_id=appointment.id))
return appointment
```
Key points in this example:
- The service owns the booking rules (check availability, set status)
- It publishes an event so other domains can react
- It raises a domain-specific error, not a generic one
- The router is not involved in any decision-making
# API Conventions
## URL Structure
```
/api/v1/{domain}/{resource}
/api/v1/{domain}/{resource}/{id}
/api/v1/{domain}/{resource}/{id}/{sub-resource}
```
- Always versioned (`v1`). Bump only for breaking changes.
- Resource names are plural and kebab-case.
- Maximum two levels of nesting. Beyond that, use query filters.
## HTTP Methods
| Method | Purpose | Success Code | Returns |
|--------|------------------|-------------|------------------|
| GET | Read one or many | 200 | Resource or list |
| POST | Create | 201 | Created resource |
| PATCH | Partial update | 200 | Updated resource |
| DELETE | Remove | 204 | Empty body |
## Response Format
List endpoints return an envelope. Single resource endpoints return the object directly.
```json
{ "data": [...], "meta": { "total": 142, "page": 1, "page_size": 20 } }
```
Pagination: `?page=1&page_size=20`. Default 20, max 100. Always include `meta.total`.
## Authentication
```
Authorization: Bearer <access_token>
```
Access tokens: 15 min. Refresh tokens: 7 days. On 401, attempt refresh before prompting login.
## Error Responses
```json
{
"error": {
"code": "SLOT_UNAVAILABLE",
"message": "The requested time slot is no longer available.",
"details": { "requested_time": "2025-03-15T10:00:00Z" }
}
}
```
- `code`: Machine-readable, UPPER_SNAKE_CASE. Clients switch on this.
- `message`: Human-readable. Safe to show in UI.
- `details`: Optional. Extra context for debugging or user guidance.
- Never expose stack traces, internal paths, or SQL in error responses.
# Database Patterns
## Table Naming
- Plural, snake_case: `appointments`, `user_sessions`, `audit_logs`
- Join tables: `{table_a}_{table_b}` alphabetically: `practices_users`
## Standard Columns
Every table gets these. No exceptions.
```sql
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
```
Always UUID, never auto-increment. Multi-tenant tables add `practice_id UUID NOT NULL REFERENCES practices(id)`.
## Index Naming
Pattern: `ix_{table}_{columns}` for regular, `ux_{table}_{columns}` for unique.
```sql
CREATE INDEX ix_appointments_provider_id ON appointments(provider_id);
CREATE UNIQUE INDEX ux_users_email ON users(email);
```
## Foreign Key Naming
Pattern: `fk_{table}_{column}`
```sql
CONSTRAINT fk_appointments_provider_id FOREIGN KEY (provider_id) REFERENCES providers(id)
```
## Enum Handling
Enums are created and managed exclusively in migrations. Never auto-created by the ORM.
```python
# Migration: create the type explicitly
status_enum = postgresql.ENUM("pending", "confirmed", "cancelled", name="appointment_status")
status_enum.create(op.get_bind(), checkfirst=True)
# Model: reference it with create_type=False
status = Column(ENUM("pending", "confirmed", "cancelled",
name="appointment_status", create_type=False))
```
Adding a value: `ALTER TYPE appointment_status ADD VALUE IF NOT EXISTS 'no_show';`
Removing a value requires type recreation. See enum lifecycle rules in `ARCHITECTURE.md`.
## Migration Rules
1. Every schema change goes through Alembic. No manual DDL in production.
2. Migrations must be reversible. Both `upgrade()` and `downgrade()` required.
3. Never modify a migration already applied to staging or production. Create a new one.
4. Data migrations are separate from schema migrations. Do not mix them.
5. Test both upgrade and downgrade paths in CI against a fresh database.
# Testing Strategy
## What to Always Test
- **Business rules.** Every conditional in a service function needs a test.
- **Authentication and authorization.** Verify that protected routes reject unauthenticated requests. Verify role-based access returns 403 for wrong roles.
- **Data isolation.** In multi-tenant systems, confirm users cannot access other tenants' data.
- **Edge cases.** Empty inputs, duplicate submissions, expired tokens, concurrent modifications.
- **Error paths.** Confirm the right error code and message for each failure mode.
## What to Skip
- Framework internals. Do not test that FastAPI parses JSON or that SQLAlchemy runs queries.
- Trivial getters and setters with no logic.
- UI layout details. Visual regression tools handle this better than unit tests.
- Third-party library behavior. Mock it at the boundary and move on.
## Test Naming
```
test_{action}_{scenario}_{expected_result}
```
Examples:
```python
def test_book_appointment_slot_taken_returns_conflict():
def test_login_expired_token_returns_401():
def test_list_patients_other_practice_returns_empty():
def test_create_invoice_negative_amount_raises_validation_error():
```
The name should read like a sentence. If you cannot describe it clearly, the test might be doing too much.
## Unit vs Integration Split
| Type | Scope | Database | Target |
|-------------|--------------------------------|----------|---------------|
| Unit | Single function or class | No (mocked) | Services, utils |
| Integration | Route through database | Yes | Routers, queries |
Aim for roughly 70% unit, 30% integration. Unit tests run in milliseconds; integration tests are slower but catch wiring issues.
## Coverage Targets
- **Aggregate floor: 80%.** Below this, the build fails.
- **Auth and billing: 95%.** These domains handle money and access. Under-testing them is a business risk.
- **New code: 90%.** Every PR should meet this for changed files.
- Coverage is a floor, not a goal. 100% coverage with bad assertions is worse than 80% with strong ones.
## Fixture Strategy
- Use factory functions, not raw fixture data: `create_test_appointment(override={"status": "cancelled"})`
- Fixtures scoped to the domain live in `domains/{name}/tests/conftest.py`
- Shared fixtures (database session, authenticated client) live in the root `conftest.py`
- Each test gets a clean database transaction that rolls back after the test
- Never share mutable state between tests. If one test modifies data, the next test should not see it.
AI skills
Herbruikbare skill bestanden die AI leren om code te genereren volgens jouw conventies.
Zet ze in .claude/skills/.
Besproken in les 11.
# Skill: Generate Domain Service
When asked to generate a service layer for a domain, follow these steps.
## Input
A Pydantic schema file (e.g., `schemas.py`) containing request/response models.
Read the schema file first. Identify the core entity name, required fields,
optional fields, and any nested models.
## Service Class Pattern
```python
from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from .models import {Entity}
from .schemas import {Entity}Create, {Entity}Update, {Entity}Response, {Entity}Filters
from .exceptions import {Entity}NotFoundError, {Entity}AlreadyExistsError
class {Entity}Service:
def __init__(self, db: AsyncSession):
self.db = db
async def create(self, data: {Entity}Create) -> {Entity}:
entity = {Entity}(**data.model_dump())
self.db.add(entity)
try:
await self.db.flush()
except IntegrityError:
raise {Entity}AlreadyExistsError(data.name)
return entity
async def get_by_id(self, entity_id: UUID) -> {Entity}:
result = await self.db.execute(
select({Entity}).where({Entity}.id == entity_id)
)
entity = result.scalar_one_or_none()
if not entity:
raise {Entity}NotFoundError(entity_id)
return entity
async def list(
self, filters: {Entity}Filters, offset: int = 0, limit: int = 50
) -> list[{Entity}]:
query = select({Entity})
for field, value in filters.model_dump(exclude_none=True).items():
query = query.where(getattr({Entity}, field) == value)
query = query.offset(offset).limit(limit)
result = await self.db.execute(query)
return list(result.scalars().all())
async def update(self, entity_id: UUID, data: {Entity}Update) -> {Entity}:
entity = await self.get_by_id(entity_id)
for field, value in data.model_dump(exclude_unset=True).items():
setattr(entity, field, value)
await self.db.flush()
return entity
async def delete(self, entity_id: UUID) -> None:
entity = await self.get_by_id(entity_id)
await self.db.delete(entity)
await self.db.flush()
```
## Rules
- Never raise HTTP exceptions in the service layer. Raise domain exceptions only.
- Accept `AsyncSession` via constructor injection. Do not create sessions internally.
- Use `flush()` instead of `commit()`. Let the caller (router) manage the transaction.
- Always return the domain model, never a dict or schema object.
- Place the service in `{domain}/service.py`.
- Place exceptions in `{domain}/exceptions.py` as subclasses of a shared `DomainError`.
# Skill: Create FastAPI Endpoint
When asked to create API endpoints for a domain, follow these conventions.
## URL Pattern
- Collection: `/{domain_plural}`, Resource: `/{domain_plural}/{id}`
- Use kebab-case for multi-word paths: `/appointment-slots`
## Route Handler Pattern
```python
from uuid import UUID
from fastapi import APIRouter, Depends, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_db, get_current_user
from app.core.auth import User
from .schemas import {Entity}Create, {Entity}Update, {Entity}Response, {Entity}Filters
from .service import {Entity}Service
router = APIRouter(prefix="/{domain_plural}", tags=["{domain}"])
@router.post("", status_code=status.HTTP_201_CREATED, response_model={Entity}Response)
async def create_{entity}(
data: {Entity}Create, db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
entity = await {Entity}Service(db).create(data)
await db.commit()
return entity
@router.get("/{{{entity}_id}}", response_model={Entity}Response)
async def get_{entity}(
{entity}_id: UUID, db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
return await {Entity}Service(db).get_by_id({entity}_id)
@router.get("", response_model=list[{Entity}Response])
async def list_{entity_plural}(
filters: {Entity}Filters = Depends(), db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
return await {Entity}Service(db).list(filters)
@router.patch("/{{{entity}_id}}", response_model={Entity}Response)
async def update_{entity}(
{entity}_id: UUID, data: {Entity}Update, db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
entity = await {Entity}Service(db).update({entity}_id, data)
await db.commit()
return entity
@router.delete("/{{{entity}_id}}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_{entity}(
{entity}_id: UUID, db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
await {Entity}Service(db).delete({entity}_id)
await db.commit()
```
## Rules
- Handlers are thin: validate input, call the service, commit, return.
- Every endpoint requires `get_current_user` unless explicitly public.
- Use `201` for create, `200` for get/list/update, `204` for delete.
- Use PATCH for partial updates, not PUT.
- Register the router in `app/main.py` with the `/api/v1` prefix.
# Skill: Scaffold a New Domain
Generate files under `app/domains/{domain_name}/`:
`__init__.py`, `models.py`, `schemas.py`, `service.py`, `router.py`,
`events.py`, `exceptions.py`, `tests/__init__.py`, `tests/test_service.py`
## File Contents
### `__init__.py`
```python
from .router import router
__all__ = ["router"]
```
### `models.py`
```python
from uuid import uuid4
from sqlalchemy import Column
from sqlalchemy.dialects.postgresql import UUID
from app.core.database import Base, TimestampMixin
class {Entity}(Base, TimestampMixin):
__tablename__ = "{table_name}"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
```
### `schemas.py`
```python
from uuid import UUID
from datetime import datetime
from pydantic import BaseModel
class {Entity}Base(BaseModel):
pass # Shared fields
class {Entity}Create({Entity}Base):
pass
class {Entity}Update(BaseModel):
pass # All optional for partial update
class {Entity}Response({Entity}Base):
id: UUID
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class {Entity}Filters(BaseModel):
pass
```
### `service.py` and `router.py`
Generate using the `generate-service` and `create-endpoint` skills.
### `events.py`
```python
from dataclasses import dataclass
from uuid import UUID
@dataclass(frozen=True)
class {Entity}Created:
entity_id: UUID
@dataclass(frozen=True)
class {Entity}Updated:
entity_id: UUID
```
### `exceptions.py`
```python
from uuid import UUID
from app.core.exceptions import DomainError
class {Entity}NotFoundError(DomainError):
def __init__(self, entity_id: UUID):
super().__init__(f"{Entity} with id {entity_id} not found")
```
## Registration
1. Add router: `app.include_router({domain_name}_router, prefix="/api/v1")`
2. Register event handlers in `app/core/events.py` if needed.
3. Create migration: `alembic revision --autogenerate -m "add {table_name}"`
4. Apply: `alembic upgrade head`
# Skill: Review Code Against Project Conventions
When asked to review code, check every item below. Report findings as a list
with severity (error, warning, info) and file:line references.
## Naming and Structure
- Models use PascalCase, columns use snake_case
- Service methods are async and accept typed arguments
- Router handlers are thin (no business logic)
- Schemas inherit from a shared Base, with separate Create/Update/Response models
- No cross-domain imports (domain A must not import from domain B directly)
## Type Safety
- All function signatures have type annotations (args and return)
- Pydantic models use strict types where appropriate (UUID, not str)
- No use of `Any` unless justified with a comment
- Query parameters use Pydantic models via `Depends()`, not loose kwargs
## Error Handling
- Services raise domain exceptions, never HTTPException
- Routers catch domain exceptions via a global exception handler
- No bare `except:` clauses; always catch specific exceptions
- Failed operations log the error before raising
## Security
- No raw SQL strings; use SQLAlchemy query builder or parameterized text()
- Every non-public endpoint has `Depends(get_current_user)`
- Sensitive fields (password, token) are excluded from response schemas
- No secrets or credentials hardcoded in source files
## Performance
- List endpoints use offset/limit pagination (default limit <= 100)
- No N+1 queries; use `selectinload` or `joinedload` for relationships
- No unnecessary eager loading on simple get-by-id queries
- Database indexes exist for columns used in WHERE and ORDER BY
## Testing
- Service tests cover: create, get, not-found, update, delete, duplicate
- Router tests cover: success path, validation error (422), not found (404)
- Tests use fixtures for database session and test data
- No tests depend on external services or network calls
## Output Format
```
[ERROR] app/domains/patient/service.py:42 - Missing return type annotation
[WARNING] app/domains/patient/router.py:15 - Endpoint missing auth dependency
[INFO] app/domains/patient/models.py:8 - Consider adding index on email column
```
List all findings. If no issues are found, state "No issues found."
Quality gate configs
Kant-en-klare configuratiebestanden uit les 8 en les 4. Kopieer naar je project root.
# .pre-commit-config.yaml
# Copy this file to your project root as .pre-commit-config.yaml
# Install: pip install pre-commit && pre-commit install
# Budget: under 5 seconds total
repos:
# General file hygiene
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-merge-conflict
- id: check-added-large-files
args: ["--maxkb=500"]
# Secret detection
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: detect-private-key
- id: detect-aws-credentials
args: ["--allow-missing-credentials"]
# Python: ruff for linting and formatting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: ["--fix", "--exit-non-zero-on-fix"]
- id: ruff-format
# TypeScript/JavaScript: prettier formatting
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, json, css, markdown]
additional_dependencies: ["prettier@3.3.0"]
#!/usr/bin/env bash
# Git pre-push hook
# Copy to .git/hooks/pre-push and make executable: chmod +x .git/hooks/pre-push
# Budget: under 60 seconds total
set -euo pipefail
echo "Running pre-push quality checks..."
# 1. Type checking with mypy
echo "[1/3] Running mypy type checks..."
mypy app/ --strict --no-error-summary
echo " mypy passed."
# 2. Unit tests (fail-fast, no slow/integration tests)
echo "[2/3] Running unit tests..."
pytest tests/unit/ -x -q --no-header --tb=short
echo " tests passed."
# 3. Security-focused lint rules
echo "[3/3] Running ruff security checks..."
ruff check app/ --select S --no-fix --quiet
echo " security lint passed."
echo "All pre-push checks passed."
# .github/workflows/quality-gates.yml
# Copy this file to .github/workflows/ in your repository.
# Budget: under 5 minutes total.
name: Quality Gates
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Ruff format check
run: ruff format --check app/ tests/
- name: Ruff lint
run: ruff check app/ tests/
- name: Mypy strict type checking
run: mypy app/ --strict
- name: Run tests with coverage
env:
DATABASE_URL: postgresql+asyncpg://test:test@localhost:5432/test_db
run: |
pytest tests/ \
--cov=app \
--cov-report=term-missing \
--cov-fail-under=80 \
-x -q
- name: Dependency vulnerability audit
run: pip-audit --strict --progress-spinner=off
#!/usr/bin/env bash
# Type safety pipeline: generate TypeScript types from the OpenAPI spec.
# Run from the project root.
# Requires: openapi-typescript (npm), tsc, curl, jq
set -euo pipefail
BACKEND_URL="${BACKEND_URL:-http://localhost:8000}"
OPENAPI_PATH="/api/v1/openapi.json"
OUTPUT_FILE="frontend/src/types/api.generated.ts"
echo "Type Safety Pipeline"
echo "===================="
# 1. Wait for backend to be reachable (up to 15 seconds)
echo "[1/4] Checking backend availability at ${BACKEND_URL}..."
for i in $(seq 1 15); do
if curl -sf "${BACKEND_URL}/health" > /dev/null 2>&1; then
echo " Backend is reachable."
break
fi
if [ "$i" -eq 15 ]; then
echo " ERROR: Backend not reachable at ${BACKEND_URL} after 15 seconds."
echo " Start the backend first, or set BACKEND_URL to the correct address."
exit 1
fi
sleep 1
done
# 2. Fetch the OpenAPI spec
echo "[2/4] Fetching OpenAPI spec..."
SPEC_FILE=$(mktemp /tmp/openapi-spec.XXXXXX.json)
curl -sf "${BACKEND_URL}${OPENAPI_PATH}" | jq '.' > "${SPEC_FILE}"
echo " Saved spec to ${SPEC_FILE} ($(jq '.paths | keys | length' "${SPEC_FILE}") endpoints)."
# 3. Generate TypeScript types
echo "[3/4] Generating TypeScript types..."
mkdir -p "$(dirname "${OUTPUT_FILE}")"
npx openapi-typescript "${SPEC_FILE}" -o "${OUTPUT_FILE}"
echo " Generated ${OUTPUT_FILE}."
rm -f "${SPEC_FILE}"
# Check for uncommitted type changes
if ! git diff --quiet -- "${OUTPUT_FILE}" 2>/dev/null; then
echo " WARNING: Generated types differ from committed version."
echo " Run 'git diff ${OUTPUT_FILE}' to inspect changes."
fi
# 4. Verify TypeScript compiles
echo "[4/4] Running TypeScript compiler check..."
cd frontend
npx tsc --noEmit
echo " TypeScript compilation passed."
echo ""
echo "Pipeline complete. Types are up to date."
Opzetten in 4 stappen
Download alle bestanden hierboven
Of clone de examples directory uit de repo.
Maak .claude/ aan in je project root
Drop de 5 template bestanden. Pas de placeholders aan voor jouw stack.
Voeg skills toe aan .claude/skills/
Deze leren AI om jouw conventies te volgen bij het genereren van code.
Installeer quality gates
Kopieer de config bestanden. Draai pip install pre-commit && pre-commit install.
Wil je de patronen achter deze bestanden begrijpen?
Begin de gratis cursus