# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Monorepo Overview

This is a pnpm workspace monorepo containing multiple product applications with shared packages. All projects use Supabase for database/auth and follow similar architectural patterns.

**Package Manager:** pnpm 9.15.0 (use `pnpm` for all commands)
**Build System:** Turborepo
**Node Version:** 20+

## Detailed Guidelines

For comprehensive guidelines on code patterns and conventions, see the `.claude/` directory:

| Document | Purpose |
|----------|---------|
| [`.claude/GUIDELINES.md`](.claude/GUIDELINES.md) | Main reference overview |
| [`.claude/guidelines/code-style.md`](.claude/guidelines/code-style.md) | Formatting, naming, linting |
| [`.claude/guidelines/database.md`](.claude/guidelines/database.md) | Drizzle ORM, schema patterns |
| [`.claude/guidelines/testing.md`](.claude/guidelines/testing.md) | Jest/Vitest, mock factories |
| [`.claude/guidelines/nestjs-backend.md`](.claude/guidelines/nestjs-backend.md) | Controllers, services, DTOs |
| [`.claude/guidelines/error-handling.md`](.claude/guidelines/error-handling.md) | Go-style Result types, error codes |
| [`.claude/guidelines/sveltekit-web.md`](.claude/guidelines/sveltekit-web.md) | Svelte 5 runes, stores |
| [`.claude/guidelines/expo-mobile.md`](.claude/guidelines/expo-mobile.md) | React Native, NativeWind |
| [`.claude/guidelines/authentication.md`](.claude/guidelines/authentication.md) | Mana Core Auth integration |
| [`.claude/guidelines/design-ux.md`](.claude/guidelines/design-ux.md) | UI patterns, animations, a11y |

**Always consult these guidelines before making changes.**

## Projects

| Project      | Description                  | Apps                                                      |
| ------------ | ---------------------------- | --------------------------------------------------------- |
| **manacore** | Multi-app ecosystem platform | Expo mobile, SvelteKit web                                |
| **manadeck** | Card/deck management         | NestJS backend, Expo mobile, SvelteKit web                |
| **picture**  | AI image generation          | Expo mobile, SvelteKit web, Astro landing                 |
| **chat**     | AI chat application          | NestJS backend, Expo mobile, SvelteKit web, Astro landing |
| **zitare**   | Daily inspiration quotes     | NestJS backend, Expo mobile, SvelteKit web, Astro landing |
| **contacts** | Contact management           | NestJS backend, SvelteKit web                             |

### Archived Projects (`apps-archived/`)

These projects are temporarily archived and excluded from the workspace. To re-activate, move back to `apps/`.

| Project            | Description                      |
| ------------------ | -------------------------------- |
| **bauntown**       | Community website for developers |
| **memoro**         | Voice memo & AI analysis         |
| **news**           | News aggregation                 |
| **nutriphi**       | Nutrition tracking               |
| **reader**         | Reading app                      |
| **uload**          | URL shortener                    |
| **wisekeep**       | AI wisdom extraction from video  |
| **techbase**       | Software comparison platform     |
| **inventory**      | Inventory management             |
| **presi**          | Presentation tool                |
| **storage**        | Cloud storage                    |

## Development Commands

For detailed local development setup, see **[docs/LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md)**.

### Quick Start (Recommended)

Use `dev:*:full` commands to start any app with automatic database setup:

```bash
pnpm docker:up           # Start PostgreSQL, Redis, MinIO
pnpm dev:chat:full       # Start chat with auth + auto DB setup
pnpm dev:zitare:full     # Start zitare with auth + auto DB setup
pnpm dev:contacts:full   # Start contacts with auth + auto DB setup
pnpm dev:calendar:full   # Start calendar with auth + auto DB setup
pnpm dev:clock:full      # Start clock with auth + auto DB setup
pnpm dev:todo:full       # Start todo with auth + auto DB setup
pnpm dev:picture:full    # Start picture with auth + auto DB setup
```

These commands automatically:
1. Create the database if missing
2. Push the latest schema
3. Start auth, backend, and web with colored output

### Database Setup

```bash
pnpm setup:db            # Setup ALL databases and schemas
pnpm setup:db:chat       # Setup just chat
pnpm setup:db:auth       # Setup just auth
```

### Individual App Commands

```bash
# Start specific project (runs all apps in project)
pnpm run manacore:dev
pnpm run manadeck:dev
pnpm run picture:dev
pnpm run chat:dev
pnpm run zitare:dev
pnpm run contacts:dev

# Start specific app within project
pnpm run dev:chat:mobile     # Just mobile app
pnpm run dev:chat:backend    # Just NestJS backend
pnpm run dev:chat:app        # Web + backend together

# Build & quality
pnpm run build
pnpm run type-check
pnpm run format
```

Each project has its own `CLAUDE.md` with detailed project-specific commands.

## Architecture Patterns

### Monorepo Structure

```
manacore-monorepo/
├── apps/                    # Active SaaS product applications
│   ├── chat/
│   │   ├── apps/
│   │   │   ├── backend/     # NestJS API
│   │   │   ├── mobile/      # Expo React Native app
│   │   │   ├── web/         # SvelteKit web app
│   │   │   └── landing/     # Astro marketing page
│   │   └── packages/        # Project-specific shared code
│   ├── manadeck/
│   ├── picture/
│   └── ...
├── apps-archived/           # Archived apps (excluded from workspace)
│   ├── bauntown/
│   ├── memoro/
│   ├── news/
│   ├── nutriphi/
│   ├── reader/
│   ├── uload/
│   └── wisekeep/
├── games/                   # Game projects
│   └── {game-name}/         # Individual games
├── services/                # Standalone microservices
│   ├── mana-core-auth/      # Central authentication service
│   ├── mana-search/         # Central search & content extraction service
│   ├── mana-crawler/        # Web crawler service
│   └── mana-llm/            # Central LLM abstraction service
├── packages/                # Monorepo-wide shared packages
└── docker/                  # Docker configuration files
```

### Standard Project Structure (inside apps/)

```
apps/{project}/
├── apps/
│   ├── backend/     # NestJS API (when present)
│   ├── mobile/      # Expo React Native app
│   ├── web/         # SvelteKit web app
│   └── landing/     # Astro marketing page
├── packages/        # Project-specific shared code
└── package.json
```

### Turborepo Configuration

**CRITICAL: Avoid Recursive Turbo Calls**

Parent workspace packages (e.g., `apps/chat/package.json`, `apps/zitare/package.json`) must **NEVER** have scripts that call `turbo run <task>` for tasks that turbo orchestrates from the root.

```jsonc
// WRONG - Creates infinite recursion!
// apps/chat/package.json
{
  "scripts": {
    "type-check": "turbo run type-check",  // DON'T DO THIS
    "build": "turbo run build",            // DON'T DO THIS
    "lint": "turbo run lint"               // DON'T DO THIS
  }
}

// CORRECT - Let root turbo handle orchestration
// apps/chat/package.json
{
  "scripts": {
    "dev": "turbo run dev"  // OK for dev (persistent task, scoped)
    // No type-check, build, lint scripts - handled by root turbo
  }
}
```

**Why this matters:** When root turbo runs `type-check`, it finds packages with `type-check` scripts and runs them. If that script is `turbo run type-check`, it spawns another turbo process that does the same thing → infinite loop. This causes tasks to run for 10+ minutes with thousands of duplicate task entries.

**The `dev` script exception:** Using `turbo run dev` in parent packages is acceptable because:
1. It's typically run directly on that package (scoped)
2. Dev tasks are persistent and turbo handles them differently

**Current turbo.json settings:**
- `concurrency: "5"` - Parallel task limit (adjust based on machine)
- `type-check` has `dependsOn: ["^type-check"]` - Dependencies are checked first

### Technology Stack by App Type

**Mobile Apps (Expo):**

- React Native 0.76-0.81 + Expo SDK 52-54
- Expo Router (file-based routing)
- NativeWind (Tailwind for React Native)
- Zustand (state management)

**Web Apps (SvelteKit):**

- SvelteKit 2.x + Svelte 5
- Tailwind CSS
- Supabase SSR auth

**Landing Pages (Astro):**

- Astro 5.x
- Tailwind CSS
- Static site generation

**Backends (NestJS):**

- NestJS 10-11
- TypeScript
- Supabase integration

### Authentication Architecture

All projects use **mana-core-auth** as the central authentication service:

```
┌─────────────┐     ┌─────────────┐     ┌────────────────┐
│   Client    │────>│  Backend    │────>│ mana-core-auth │
│ (Web/Mobile)│     │  (NestJS)   │     │  (port 3001)   │
└─────────────┘     └─────────────┘     └────────────────┘
      │                   │                     │
      │ Bearer token      │ POST /validate      │
      │                   │ {token}             │
      │                   │<────────────────────│
      │                   │ {valid, payload}    │
      │<──────────────────│                     │
      │ Response          │                     │
```

#### Key Components

| Component                       | Purpose                                            |
| ------------------------------- | -------------------------------------------------- |
| `services/mana-core-auth`       | Central auth service (Better Auth + EdDSA JWT)     |
| `@manacore/shared-nestjs-auth`  | Shared NestJS guards/decorators for JWT validation |
| `@mana-core/nestjs-integration` | Extended NestJS module with auth + credits         |
| `@manacore/shared-auth`         | Client-side auth for web/mobile apps               |

#### NestJS Backend Integration

**Option 1: Simple auth only** - Use `@manacore/shared-nestjs-auth`:

```typescript
// In your controller
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';

@Controller('api')
@UseGuards(JwtAuthGuard)
export class MyController {
	@Get('profile')
	getProfile(@CurrentUser() user: CurrentUserData) {
		return { userId: user.userId, email: user.email };
	}
}
```

**Option 2: Auth + Credits** - Use `@mana-core/nestjs-integration`:

```typescript
// app.module.ts
import { ManaCoreModule } from '@mana-core/nestjs-integration';

@Module({
	imports: [
		ManaCoreModule.forRootAsync({
			imports: [ConfigModule],
			useFactory: (config: ConfigService) => ({
				appId: config.get('APP_ID'),
				serviceKey: config.get('MANA_CORE_SERVICE_KEY'),
				debug: config.get('NODE_ENV') === 'development',
			}),
			inject: [ConfigService],
		}),
	],
})
export class AppModule {}

// In controller
import { AuthGuard } from '@mana-core/nestjs-integration/guards';
import { CurrentUser } from '@mana-core/nestjs-integration/decorators';
import { CreditClientService } from '@mana-core/nestjs-integration';

@Controller('api')
@UseGuards(AuthGuard)
export class ApiController {
	constructor(private creditClient: CreditClientService) {}

	@Post('generate')
	async generate(@CurrentUser() user: any) {
		await this.creditClient.consumeCredits(user.sub, 'generation', 10, 'AI generation');
		// ... do work
	}
}
```

#### Required Environment Variables

```env
# All backends need this
MANA_CORE_AUTH_URL=http://localhost:3001

# For development bypass (optional)
NODE_ENV=development
DEV_BYPASS_AUTH=true
DEV_USER_ID=your-test-user-id

# For credit operations (optional)
MANA_CORE_SERVICE_KEY=your-service-key
APP_ID=your-app-id
```

#### JWT Token Structure (EdDSA)

```json
{
	"sub": "user-id",
	"email": "user@example.com",
	"role": "user",
	"sid": "session-id",
	"exp": 1764606251,
	"iss": "manacore",
	"aud": "manacore"
}
```

#### Testing Auth Integration

```bash
# 1. Start mana-core-auth
pnpm dev:auth

# 2. Start a backend (e.g., Zitare)
pnpm dev:zitare:backend

# 3. Get a token
TOKEN=$(curl -s -X POST http://localhost:3001/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "password": "password"}' | jq -r '.accessToken')

# 4. Call protected endpoint
curl http://localhost:3007/api/favorites \
  -H "Authorization: Bearer $TOKEN"
```

#### Integrated Backends

| Backend  | Package                         | Port |
| -------- | ------------------------------- | ---- |
| Chat     | `@mana-core/nestjs-integration` | 3002 |
| Picture  | `@manacore/shared-nestjs-auth`  | 3006 |
| Zitare   | `@manacore/shared-nestjs-auth`  | 3007 |
| Presi    | Custom (same pattern)           | 3008 |
| ManaDeck | `@mana-core/nestjs-integration` | 3009 |

### Search Architecture

Projects requiring web search and content extraction use **mana-search** as the central search service:

```
┌─────────────────────────────────────────────────────────────┐
│                 Consumer Apps                                │
│   Questions │ Chat │ Project Doc Bot │ Future Apps          │
└─────────────────────────┬───────────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────────┐
│              mana-search (Port 3021)                         │
│   Search API │ Extract API │ Redis Cache                    │
└─────────────────────────┬───────────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────────┐
│              SearXNG (Port 8080, internal)                   │
│   Google │ Bing │ DuckDuckGo │ Wikipedia │ arXiv │ ...      │
└─────────────────────────────────────────────────────────────┘
```

#### Key Components

| Component | Purpose |
|-----------|---------|
| `services/mana-search` | NestJS search service with SearXNG + Redis |
| SearXNG | Meta-search engine aggregating multiple sources |
| Redis | Caching layer (search: 1h TTL, extract: 24h TTL) |

#### API Endpoints

```bash
# Web search
POST /api/v1/search
{
  "query": "quantum computing",
  "options": {
    "categories": ["general", "science"],
    "engines": ["google", "wikipedia"],
    "limit": 10
  }
}

# Extract content from URL
POST /api/v1/extract
{
  "url": "https://example.com/article",
  "options": { "includeMarkdown": true }
}

# Bulk extract (max 20 URLs)
POST /api/v1/extract/bulk

# Health & metrics
GET /health
GET /metrics
```

#### Search Categories

| Category | Engines |
|----------|---------|
| `general` | Google, Bing, DuckDuckGo, Brave, Wikipedia |
| `news` | Google News, Bing News |
| `science` | arXiv, Google Scholar, PubMed, Semantic Scholar |
| `it` | GitHub, StackOverflow, NPM, MDN |

#### Starting the Service

```bash
# Start SearXNG + Redis (for local NestJS development)
cd services/mana-search && docker-compose -f docker-compose.dev.yml up -d

# Start NestJS API
pnpm --filter @mana-search/service dev

# Or start everything via Docker
cd services/mana-search && docker-compose up -d
```

#### Environment Variables

```env
# Consumer apps need this
MANA_SEARCH_URL=http://localhost:3021

# mana-search service config
SEARXNG_URL=http://localhost:8080
REDIS_HOST=localhost
REDIS_PORT=6379
CACHE_SEARCH_TTL=3600
CACHE_EXTRACT_TTL=86400
```

#### Usage in Backend

```typescript
// Direct fetch
const response = await fetch('http://mana-search:3021/api/v1/search', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query: 'machine learning basics',
    options: { categories: ['general', 'science'], limit: 5 }
  })
});

const { results, meta } = await response.json();
```

### Svelte 5 Runes Mode (Web Apps)

All SvelteKit apps use Svelte 5 runes:

```typescript
// CORRECT - Svelte 5
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
	console.log(count);
});

// WRONG - Old Svelte syntax
let count = 0;
$: doubled = count * 2;
```

## Shared Packages (`packages/`)

| Package                         | Purpose                                         |
| ------------------------------- | ----------------------------------------------- |
| `@manacore/shared-nestjs-auth`  | NestJS JWT validation guards via mana-core-auth |
| `@mana-core/nestjs-integration` | NestJS module with auth guards + credit client  |
| `@manacore/shared-auth`         | Client-side auth service for web/mobile apps    |
| `@manacore/shared-storage`      | S3-compatible storage (MinIO local, Hetzner prod) |
| `@manacore/shared-supabase`     | Unified Supabase client                         |
| `@manacore/shared-types`        | Common TypeScript types                         |
| `@manacore/shared-utils`        | Utility functions                               |
| `@manacore/shared-ui`           | React Native UI components                      |
| `@manacore/shared-theme`        | Theme configuration                             |
| `@manacore/shared-i18n`         | Internationalization                            |

Import shared packages:

```typescript
import { createAuthService } from '@manacore/shared-auth';
import { formatDate, truncate } from '@manacore/shared-utils';
```

## Database (Supabase)

- All projects use Supabase for PostgreSQL database, auth, and storage
- Row Level Security (RLS) policies enforce access control via JWT claims
- Each project has its own Supabase project/schema
- Types typically generated via `supabase gen types`

## Object Storage (MinIO / Hetzner)

S3-compatible object storage for file uploads, generated images, etc.

### Architecture

| Environment | Service | Purpose |
|-------------|---------|---------|
| **Local** | MinIO (Docker) | S3-compatible local storage |
| **Production** | Hetzner Object Storage | Cost-effective S3-compatible cloud storage |

### Local Development

```bash
# Start infrastructure (includes MinIO)
pnpm docker:up

# MinIO Web Console: http://localhost:9001
# Username: minioadmin
# Password: minioadmin

# S3 API endpoint: http://localhost:9000
```

### Pre-configured Buckets

| Bucket | Project | Purpose |
|--------|---------|---------|
| `picture-storage` | Picture | AI-generated images |
| `chat-storage` | Chat | User file uploads |
| `manadeck-storage` | ManaDeck | Card/deck assets |
| `nutriphi-storage` | NutriPhi | Meal photos |
| `presi-storage` | Presi | Presentation slides |
| `calendar-storage` | Calendar | Calendar attachments |
| `contacts-storage` | Contacts | Contact avatars/files |
| `storage-storage` | Storage | Cloud drive files |

### Usage in Backend

```typescript
import { createPictureStorage, generateUserFileKey, getContentType } from '@manacore/shared-storage';

const storage = createPictureStorage();

// Upload
const key = generateUserFileKey(userId, 'image.png');
const result = await storage.upload(key, buffer, {
  contentType: getContentType('image.png'),
  public: true,
});

// Download
const data = await storage.download(key);

// Presigned URLs
const uploadUrl = await storage.getUploadUrl(key, { expiresIn: 3600 });
```

### Environment Variables

```env
# Local (in .env.development)
S3_ENDPOINT=http://localhost:9000
S3_REGION=us-east-1
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin

# Production (Hetzner)
S3_ENDPOINT=https://fsn1.your-objectstorage.com
S3_REGION=fsn1
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
```

## Landing Pages (Cloudflare Pages)

All landing pages are deployed to Cloudflare Pages using Direct Upload via Wrangler CLI.

### Landing Pages

| Project | Package | Cloudflare Project | URL |
|---------|---------|-------------------|-----|
| Chat | `@chat/landing` | `chat-landing` | https://chat-landing.pages.dev |
| Picture | `@picture/landing` | `picture-landing` | https://picture-landing.pages.dev |
| ManaCore | `@manacore/landing` | `manacore-landing` | https://manacore-landing.pages.dev |
| ManaDeck | `@manadeck/landing` | `manadeck-landing` | https://manadeck-landing.pages.dev |
| Zitare | `@zitare/landing` | `zitare-landing` | https://zitare-landing.pages.dev |

### Local Deployment

```bash
# First time: Login to Cloudflare
pnpm cf:login

# Create projects (one-time setup)
pnpm cf:projects:create

# Deploy individual landing page
pnpm deploy:landing:chat
pnpm deploy:landing:picture
pnpm deploy:landing:manacore
pnpm deploy:landing:manadeck
pnpm deploy:landing:zitare

# Deploy all landing pages
pnpm deploy:landing:all

# List all projects
pnpm cf:projects:list
```

### Adding New Landing Pages

1. Create the landing page in `apps/{project}/apps/landing/`
2. Add `wrangler.toml`:
   ```toml
   name = "{project}-landing"
   compatibility_date = "2024-12-01"
   pages_build_output_dir = "dist"
   ```
3. Add deploy script to root `package.json`:
   ```json
   "deploy:landing:{project}": "pnpm --filter @{project}/landing build && npx wrangler pages deploy apps/{project}/apps/landing/dist --project-name={project}-landing"
   ```
4. Create Cloudflare project: `npx wrangler pages project create {project}-landing --production-branch=main`

### Custom Domains

```bash
# Add custom domain to a project
npx wrangler pages project add-domain chat-landing chat.manacore.app
```

## Server Access

### Mac Mini Production Server

The production environment runs on a Mac Mini, accessible via Cloudflare Tunnel.

**Domain:** mana.how
**SSH:** `ssh mana-server` (requires cloudflared and SSH config)

```bash
# SSH config (~/.ssh/config)
Host mana-server
    HostName mac-mini.mana.how
    User till
    ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h
```

#### Useful Commands

```bash
ssh mana-server                              # Connect to server
cd ~/projects/manacore-monorepo

./scripts/mac-mini/status.sh                 # Check all services
./scripts/mac-mini/deploy.sh                 # Pull & restart containers
./scripts/mac-mini/health-check.sh           # Run health checks
docker compose -f docker-compose.macmini.yml logs -f  # View logs
```

For detailed server documentation, see **[docs/MAC_MINI_SERVER.md](docs/MAC_MINI_SERVER.md)**.

## Adding Dependencies

```bash
# Add to workspace root (dev tools only)
pnpm add -D <package> -w

# Add to specific project
pnpm add <package> --filter memoro

# Add to specific app within project
pnpm add <package> --filter @memoro/mobile

# Add to shared package
pnpm add <package> --filter @manacore/shared-utils
```

## Environment Variables

### Centralized Development Environment

All development environment variables are managed from a single file: `.env.development`

```bash
# First-time setup: generates all app-specific .env files
pnpm setup:env

# This also runs automatically after `pnpm install`
```

The script reads `.env.development` and generates platform-specific `.env` files for each app with the correct prefixes:

- **Expo mobile**: `EXPO_PUBLIC_*` prefix
- **SvelteKit web**: `PUBLIC_*` prefix
- **NestJS backend**: No prefix

### Key Files

- `.env.development` - Central source of truth (committed to git)
- `scripts/generate-env.mjs` - Generation script
- `apps/**/apps/**/.env` - Generated files (gitignored)

### Adding New Variables

1. Add the variable to `.env.development`
2. Update `scripts/generate-env.mjs` to map it to the appropriate apps
3. Run `pnpm setup:env` to regenerate

### Platform Prefix Patterns

**Mobile (Expo):**

```
EXPO_PUBLIC_SUPABASE_URL=...
EXPO_PUBLIC_SUPABASE_ANON_KEY=...
EXPO_PUBLIC_MIDDLEWARE_API_URL=...
```

**Web (SvelteKit):**

```
PUBLIC_SUPABASE_URL=...
PUBLIC_SUPABASE_ANON_KEY=...
```

**Backend (NestJS):**

```
SUPABASE_URL=...
SUPABASE_SERVICE_ROLE_KEY=...
PORT=...
```

## Project-Specific Documentation

- **[docs/LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md)** - Database setup and `dev:*:full` commands
- **[docs/ENVIRONMENT_VARIABLES.md](docs/ENVIRONMENT_VARIABLES.md)** - Complete environment setup guide
- **[docs/DATABASE_MIGRATIONS.md](docs/DATABASE_MIGRATIONS.md)** - Migration best practices, CI/CD, rollback procedures

Each project has its own `CLAUDE.md` with detailed information:

- `apps/manacore/CLAUDE.md` - Multi-app ecosystem, auth details
- `apps/manadeck/CLAUDE.md` - Card/deck management
- `apps/chat/CLAUDE.md` - Chat API endpoints, AI models
- `apps/picture/CLAUDE.md` - AI image generation
- `services/mana-core-auth/` - Central authentication service
- `services/mana-search/CLAUDE.md` - Search & content extraction service
- `services/mana-crawler/CLAUDE.md` - Web crawler service
- `services/mana-llm/CLAUDE.md` - Central LLM abstraction service

Navigate to the specific project directory to work on it.

## Code Quality Infrastructure (TODO)

A detailed plan for code quality tooling is available at `.claude/plans/proud-dancing-moon.md`. When ready to implement:

### Planned Setup

- **Pre-commit hooks**: Husky + lint-staged (format + lint on commit)
- **Commit messages**: Commitlint with Conventional Commits (`feat:`, `fix:`, `docs:`, etc.)
- **CI Pipeline**: GitHub Actions PR checks (lint, format, type-check, tests)
- **Formatting**: Tabs, single quotes, 100 char width (unified across all projects)
- **Test coverage**: 80% minimum for new code (once testing infrastructure is in place)

### Key Files to Create

```
.husky/pre-commit          # Run lint-staged
.husky/commit-msg          # Run commitlint
commitlint.config.js       # Conventional commit rules
.github/workflows/pr-check.yml  # CI pipeline
packages/eslint-config/    # Shared ESLint configuration
```

### Current State

- Testing: ~25 test files total (sparse coverage)
- Linting: Fragmented configs across projects
- CI: Only 2 backend deployment workflows exist
- Pre-commit: Only maerchenzauber has Husky (SSH URL fixer only)
