diff --git a/.claude/AGENTS.go.md b/.claude/AGENTS.go.md new file mode 100644 index 0000000..169d6dc --- /dev/null +++ b/.claude/AGENTS.go.md @@ -0,0 +1,227 @@ +# AGENTS.go.md — Go Agent Rules + +Applies to: `ai-compliance-sdk/` (Go/Gin service) + +--- + +## NON-NEGOTIABLE: Pre-Push Checklist + +**BEFORE every `git push`, run ALL of the following from the module root. A single failure blocks the push.** + +```bash +# 1. Format (gofmt is non-negotiable — unformatted code fails CI) +gofmt -l . | grep -q . && echo "FORMATTING ERRORS — run: gofmt -w ." && exit 1 || true + +# 2. Vet (catches suspicious code that compiles but is likely wrong) +go vet ./... + +# 3. Lint (golangci-lint aggregates 50+ linters — the de-facto standard) +golangci-lint run --timeout=5m ./... + +# 4. Tests with race detector +go test -race -count=1 ./... + +# 5. Build verification (catches import errors, missing implementations) +go build ./... +``` + +**One-liner pre-push gate:** +```bash +gofmt -l . | grep -q . && exit 1; go vet ./... && golangci-lint run --timeout=5m && go test -race -count=1 ./... && go build ./... +``` + +### Why each check matters + +| Check | Catches | Time | +|-------|---------|------| +| `gofmt` | Formatting violations (CI rejects unformatted code) | <1s | +| `go vet` | Printf format mismatches, unreachable code, shadowed vars | <5s | +| `golangci-lint` | 50+ static analysis checks (errcheck, staticcheck, etc.) | 10-30s | +| `go test -race` | Race conditions (invisible without this flag) | 10-60s | +| `go build` | Import errors, interface mismatches | <5s | + +--- + +## golangci-lint Configuration + +Config lives in `.golangci.yml` at the repo root. Minimum required linters: + +```yaml +linters: + enable: + - errcheck # unchecked errors are bugs + - gosimple # code simplification + - govet # go vet findings + - ineffassign # useless assignments + - staticcheck # advanced static analysis (SA*, S*, QF*) + - unused # unused code + - gofmt # formatting + - goimports # import organization + - gocritic # opinionated style checks + - noctx # HTTP requests without context + - bodyclose # unclosed HTTP response bodies + - exhaustive # exhaustive switch on enums + - wrapcheck # errors from external packages must be wrapped + +linters-settings: + errcheck: + check-blank: true # blank identifier for errors is a bug + govet: + enable-all: true + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +``` + +**Never suppress with `//nolint:` without a comment explaining why it's safe.** + +--- + +## Code Structure (Hexagonal Architecture) + +``` +ai-compliance-sdk/ +├── cmd/ +│ └── server/main.go # thin: parse flags, wire deps, call app.Run() +├── internal/ +│ ├── app/ # dependency wiring +│ ├── domain/ # pure business logic, no framework deps +│ ├── ports/ # interfaces (repositories, external services) +│ ├── adapters/ +│ │ ├── http/ # Gin handlers (≤30 LOC per handler) +│ │ ├── postgres/ # DB adapters implementing ports +│ │ └── external/ # third-party API clients +│ └── services/ # orchestration between domain + ports +└── pkg/ # exported, reusable packages +``` + +**Handler constraint — max 30 lines per handler:** +```go +func (h *RiskHandler) GetRisk(c *gin.Context) { + id, err := uuid.Parse(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) + return + } + risk, err := h.service.Get(c.Request.Context(), id) + if err != nil { + h.handleError(c, err) + return + } + c.JSON(http.StatusOK, risk) +} +``` + +--- + +## Error Handling + +```go +// REQUIRED: wrap errors with context +if err != nil { + return fmt.Errorf("get risk %s: %w", id, err) +} + +// REQUIRED: define sentinel errors in domain package +var ErrNotFound = errors.New("not found") +var ErrUnauthorized = errors.New("unauthorized") + +// REQUIRED: check errors — never use _ for error returns +result, err := service.Do(ctx, input) +if err != nil { + // handle it +} +``` + +**`errcheck` linter enforces this — zero tolerance for unchecked errors.** + +--- + +## Testing Requirements + +``` +internal/ +├── domain/ +│ ├── risk.go +│ └── risk_test.go # unit: pure functions, no I/O +├── adapters/ +│ ├── http/ +│ │ ├── handler.go +│ │ └── handler_test.go # httptest-based, mock service +│ └── postgres/ +│ ├── repo.go +│ └── repo_test.go # integration: testcontainers or real DB +``` + +**Test naming convention:** +```go +func TestRiskService_Get_ReturnsRisk(t *testing.T) {} +func TestRiskService_Get_NotFound_ReturnsError(t *testing.T) {} +func TestRiskService_Get_DBError_WrapsError(t *testing.T) {} +``` + +**Table-driven tests are mandatory for functions with multiple cases:** +```go +func TestValidateInput(t *testing.T) { + tests := []struct { + name string + input string + wantErr bool + }{ + {"valid", "ok", false}, + {"empty", "", true}, + {"too long", strings.Repeat("x", 300), true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateInput(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("got err=%v, wantErr=%v", err, tt.wantErr) + } + }) + } +} +``` + +```bash +# Pre-push: unit tests only (fast) +go test -race -count=1 -run "^TestUnit" ./... + +# CI: all tests +go test -race -count=1 -coverprofile=coverage.out ./... +go tool cover -func=coverage.out | grep total +``` + +--- + +## Context Propagation + +Every function that does I/O (DB, HTTP, file) **must** accept and pass `context.Context` as the first argument: + +```go +// REQUIRED +func (r *RiskRepo) Get(ctx context.Context, id uuid.UUID) (*Risk, error) { + return r.db.QueryRowContext(ctx, query, id).Scan(...) +} + +// FORBIDDEN — no context +func (r *RiskRepo) Get(id uuid.UUID) (*Risk, error) { ... } +``` + +`noctx` linter enforces HTTP client context. Manual review required for DB calls. + +--- + +## Common Pitfalls That Break CI + +| Pitfall | Prevention | +|---------|------------| +| Unformatted code | `gofmt -w .` before commit | +| Unchecked error return from `rows.Close()` / `resp.Body.Close()` | `errcheck` + `bodyclose` linters | +| Goroutine leak (goroutine started but never stopped) | `-race` test flag | +| Shadowed `err` variable in nested scope | `govet -shadow` | +| HTTP response body not closed | `bodyclose` linter | +| `interface{}` instead of `any` (Go 1.18+) | `gocritic` | +| Missing context on DB/HTTP calls | `noctx` linter | +| Returning concrete type from constructor instead of interface | breaks testability | diff --git a/.claude/AGENTS.python.md b/.claude/AGENTS.python.md new file mode 100644 index 0000000..71a8525 --- /dev/null +++ b/.claude/AGENTS.python.md @@ -0,0 +1,157 @@ +# AGENTS.python.md — Python Agent Rules + +Applies to: `backend-compliance/`, `ai-compliance-sdk/` (Python path), `compliance-tts-service/`, `document-crawler/`, `dsms-gateway/` (Python services) + +--- + +## NON-NEGOTIABLE: Pre-Push Checklist + +**BEFORE every `git push`, run ALL of the following from the service directory. A single failure blocks the push.** + +```bash +# 1. Fast lint (Ruff — catches syntax errors, unused imports, style violations) +ruff check . + +# 2. Auto-fix safe issues, then re-check +ruff check --fix . && ruff check . + +# 3. Type checking (mypy strict on new modules, standard on legacy) +mypy . --ignore-missing-imports --no-error-summary + +# 4. Unit tests only (fast, no external deps) +pytest tests/unit/ -x -q --no-header + +# 5. Verify the service starts (catches import errors, missing env vars with defaults) +python -c "import app" 2>/dev/null || python -c "import main" 2>/dev/null || true +``` + +**One-liner pre-push gate (run from service root):** +```bash +ruff check . && mypy . --ignore-missing-imports --no-error-summary && pytest tests/ -x -q --no-header +``` + +### Why each check matters + +| Check | Catches | Time | +|-------|---------|------| +| `ruff check` | Syntax errors, unused imports, undefined names | <2s | +| `mypy` | Type mismatches, wrong argument types | 5-15s | +| `pytest -x` | Logic errors, regressions | 10-60s | +| import check | Missing packages, circular imports | <1s | + +--- + +## Code Style (Ruff) + +Config lives in `pyproject.toml`. Do **not** add per-file `# noqa` suppressions without a comment explaining why. + +```toml +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM", "TCH"] +ignore = ["E501"] # line length handled by formatter + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["S101"] # assert is fine in tests +``` + +**Blocked patterns:** +- `from module import *` — always name imports explicitly +- Bare `except:` — use `except Exception as e:` at minimum +- `print()` in production code — use `logger` +- Mutable default arguments: `def f(x=[])` → `def f(x=None)` + +--- + +## Type Annotations + +All new functions **must** have complete type annotations. Use `from __future__ import annotations` for forward references. + +```python +# Required +async def get_tenant(tenant_id: str, db: AsyncSession) -> TenantModel | None: + ... + +# Required for complex types +from typing import Sequence +def list_risks(filters: dict[str, str]) -> Sequence[RiskModel]: + ... +``` + +**Mypy rules:** +- `--disallow-untyped-defs` on new files +- `--strict` on new modules (not legacy) +- Never use `type: ignore` without a comment + +--- + +## FastAPI-Specific Rules + +```python +# Handlers stay thin — delegate to service layer +@router.get("/risks/{risk_id}", response_model=RiskResponse) +async def get_risk(risk_id: UUID, service: RiskService = Depends(get_risk_service)): + return await service.get(risk_id) # ≤5 lines per handler + +# Always use response_model — never return raw dicts from endpoints +# Always validate input with Pydantic — no manual dict parsing +# Use HTTPException with specific status codes, never bare 500 +``` + +--- + +## Testing Requirements + +``` +tests/ +├── unit/ # Pure logic tests, no DB/HTTP (run on every push) +├── integration/ # Requires running services (run in CI only) +└── contracts/ # OpenAPI snapshot tests (run on API changes) +``` + +**Unit test requirements:** +- Every new function → at least one happy-path test +- Every bug fix → regression test that would have caught it +- Mock all I/O: DB calls, HTTP calls, filesystem reads + +```bash +# Run unit tests only (fast, for pre-push) +pytest tests/unit/ -x -q + +# Run with coverage (for CI) +pytest tests/ --cov=. --cov-report=term-missing --cov-fail-under=70 +``` + +--- + +## Dependency Management + +```bash +# Check new package license before adding +pip show | grep -E "License|Home-page" + +# After adding to requirements.txt — verify no GPL/AGPL +pip-licenses --fail-on="GPL;AGPL" 2>/dev/null || echo "Check licenses manually" +``` + +**Never add:** +- GPL/AGPL licensed packages +- Packages with known CVEs (`pip audit`) +- Packages that only exist for dev (`pytest`, `ruff`) to production requirements + +--- + +## Common Pitfalls That Break CI + +| Pitfall | Prevention | +|---------|------------| +| `const x = ...` inside dict literal (wrong language!) | Run ruff before push | +| Pydantic v1 syntax in v2 project | Use `model_config`, not `class Config` | +| Sync function called inside async without `run_in_executor` | mypy + async linter | +| Missing `await` on coroutine | mypy catches this | +| `datetime.utcnow()` (deprecated) | Use `datetime.now(timezone.utc)` | +| Bare `except:` swallowing errors silently | ruff B001/E722 catches this | +| Unused imports left in committed code | ruff F401 catches this | diff --git a/.claude/AGENTS.typescript.md b/.claude/AGENTS.typescript.md new file mode 100644 index 0000000..ade31b1 --- /dev/null +++ b/.claude/AGENTS.typescript.md @@ -0,0 +1,186 @@ +# AGENTS.typescript.md — TypeScript/Next.js Agent Rules + +Applies to: `pitch-deck/`, `admin-v2/` (Next.js apps in this repo) + +--- + +## NON-NEGOTIABLE: Pre-Push Checklist + +**BEFORE every `git push`, run ALL of the following from the Next.js app directory. A single failure blocks the push.** + +```bash +# 1. Type check (catches the class of bug that broke ChatFAB.tsx — const inside object) +npx tsc --noEmit + +# 2. Lint (ESLint with TypeScript-aware rules) +npm run lint + +# 3. Production build (THE most important check — passes lint/types but still fails build) +npm run build +``` + +**One-liner pre-push gate:** +```bash +npx tsc --noEmit && npm run lint && npm run build +``` + +> **Why `npm run build` is mandatory:** Next.js performs additional checks during build (server component boundaries, missing env vars referenced in code, RSC/client component violations) that `tsc` and ESLint alone do not catch. The ChatFAB syntax error (`const` inside object literal) is exactly the kind of error caught only by build. + +### Why each check matters + +| Check | Catches | Time | +|-------|---------|------| +| `tsc --noEmit` | Type errors, wrong prop types, missing members | 5-20s | +| `eslint` | React hooks rules, import order, unused vars | 5-15s | +| `next build` | Server/client boundary violations, missing deps, syntax errors in JSX, env var issues | 30-120s | + +--- + +## TypeScript Configuration + +`tsconfig.json` must have strict mode enabled: + +```json +{ + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + } +} +``` + +**Never use `// @ts-ignore` or `// @ts-expect-error` without a comment explaining why it's unavoidable.** + +--- + +## ESLint Configuration + +```json +{ + "extends": [ + "next/core-web-vitals", + "plugin:@typescript-eslint/recommended-type-checked" + ], + "rules": { + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/await-thenable": "error", + "react-hooks/exhaustive-deps": "error", + "no-console": "warn" + } +} +``` + +**`@typescript-eslint/no-floating-promises`** — catches `await`-less async calls that silently swallow errors. +**`react-hooks/exhaustive-deps`** — catches missing deps in `useEffect`/`useCallback` (source of stale closure bugs). + +--- + +## Next.js 15 Rules (App Router) + +### Server vs Client boundary + +```typescript +// Server Component (default) — no 'use client' needed +// Can: fetch data, access DB, read env vars, import server-only packages +async function Page() { + const data = await fetchData() // direct async/await + return +} + +// Client Component — must have 'use client' at top +'use client' +// Can: use hooks, handle events, access browser APIs +// Cannot: import server-only packages (nodemailer, fs, db pool) +``` + +**Common violation:** Importing `lib/email.ts` (which imports nodemailer) from a client component → use `lib/email-templates.ts` instead. + +### Route Handler typing + +```typescript +// Always type request and use NextResponse +export async function GET(request: Request): Promise { + const { searchParams } = new URL(request.url) + return NextResponse.json({ data }) +} +``` + +### Environment variables + +```typescript +// Server-only env vars: access directly +const secret = process.env.PITCH_ADMIN_SECRET // fine in server components + +// Client env vars: must be prefixed NEXT_PUBLIC_ +const url = process.env.NEXT_PUBLIC_API_URL // accessible in browser + +// Never access server-only env vars in 'use client' components +``` + +--- + +## Component Architecture + +``` +app/ +├── (route-group)/ +│ ├── page.tsx # Server Component — data fetching +│ └── _components/ # Colocated components for this route +│ ├── ClientThing.tsx # 'use client' when needed +│ └── ServerThing.tsx # Server by default +components/ +│ └── ui/ # Shared presentational components +lib/ +│ ├── server-only-module.ts # import 'server-only' at top +│ └── shared-module.ts # safe for both server and client +``` + +**Rules:** +- Push `'use client'` boundary as deep as possible (toward leaves) +- Never import server-only modules from client components +- Colocate `_components/` and `_hooks/` per route when they're route-specific + +--- + +## Testing Requirements + +```bash +# Type check (fastest, run first) +npx tsc --noEmit + +# Unit tests (Vitest) +npx vitest run + +# E2E tests (Playwright — CI only, requires running server) +npx playwright test +``` + +**Test every:** +- Custom hook (`usePresenterMode`, `useSlideNavigation`) +- Utility function (`lib/auth.ts` helpers, `lib/email-templates.ts`) +- API route handler (mock DB, assert response shape) + +--- + +## Common Pitfalls That Break CI + +| Pitfall | Prevention | +|---------|------------| +| `const x = ...` inside object literal | `tsc --noEmit` + `npm run build` | +| Server-only import in client component | `import 'server-only'` guard + ESLint | +| Missing `await` on async function call | `@typescript-eslint/no-floating-promises` | +| `useEffect` with missing dependency | `react-hooks/exhaustive-deps` error | +| `any` type hiding type errors | `@typescript-eslint/no-explicit-any` error | +| Unused variable left after refactor | `noUnusedLocals` in tsconfig | +| `process.env.SECRET` in client component | Next.js build error | +| Forgetting `export default` on page component | Next.js build error | +| Calling server action from server component | must use route handler instead | +| `jose` full import in Edge Runtime | Use specific subpath: `jose/jwt/verify` | diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 16d8886..b66a21c 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -287,6 +287,37 @@ git push origin main && git push gitea main --- +## Pre-Push Checks (PFLICHT — VOR JEDEM PUSH) + +> Full detail: `.claude/rules/pre-push-checks.md` | Stack rules: `AGENTS.python.md`, `AGENTS.go.md`, `AGENTS.typescript.md` + +**NIEMALS pushen ohne diese Checks. CI-Failures blockieren das gesamte Deploy.** + +### Python (backend-core, rag-service, embedding-service, control-pipeline) + +```bash +cd +ruff check . && mypy . --ignore-missing-imports --no-error-summary && pytest tests/ -x -q --no-header +``` + +### Go (consent-service, billing-service) + +```bash +cd +gofmt -l . | grep -q . && exit 1; go vet ./... && golangci-lint run --timeout=5m && go test -race ./... && go build ./... +``` + +### TypeScript/Next.js (pitch-deck, admin-v2) + +```bash +cd pitch-deck # or admin-v2 +npx tsc --noEmit && npm run lint && npm run build +``` + +> `npm run build` ist PFLICHT — `tsc` allein reicht nicht. Syntax-Fehler wie `const` inside object literal werden nur vom Build gefangen. + +--- + ## Kernprinzipien ### 1. Open Source Policy diff --git a/.claude/rules/pre-push-checks.md b/.claude/rules/pre-push-checks.md new file mode 100644 index 0000000..7c39592 --- /dev/null +++ b/.claude/rules/pre-push-checks.md @@ -0,0 +1,74 @@ +# Pre-Push Checks (MANDATORY) + +## Rule + +**NEVER push to any remote without first running and confirming ALL checks pass for every changed language stack.** + +This rule exists because CI failures break the deploy pipeline for everyone and waste ~5 minutes per failed build. A 60-second local check prevents that. + +--- + +## Quick Reference by Stack + +### Python (backend-compliance, ai-compliance-sdk, compliance-tts-service) + +```bash +cd +ruff check . && mypy . --ignore-missing-imports --no-error-summary && pytest tests/ -x -q --no-header +``` + +Blocks on: syntax errors, type errors, failing tests. + +### Go (ai-compliance-sdk Go path) + +```bash +cd +gofmt -l . | grep -q . && exit 1; go vet ./... && golangci-lint run --timeout=5m && go test -race ./... && go build ./... +``` + +Blocks on: formatting, vet findings, lint violations, test failures, build errors. + +### TypeScript/Next.js (admin-compliance, developer-portal) + +```bash +cd +npx tsc --noEmit && npm run lint && npm run build +``` + +Blocks on: type errors, lint violations, **build failures**. + +> `npm run build` is mandatory — `tsc` passes but `next build` fails more often than you'd expect (server/client boundary violations, env var issues, JSX syntax errors). + +--- + +## What Claude Must Do Before Every Push + +1. Identify which services/apps were changed in this task +2. Run the appropriate gate command(s) from the table above +3. If any check fails: fix it, re-run, confirm green +4. Only then run `git push origin main` + +**No exceptions.** A push that skips pre-push checks and breaks CI is worse than a delayed push. + +--- + +## CI vs Local Checks + +| Stage | Where | What | +|-------|-------|------| +| Pre-push (local) | Claude runs | Lint + type check + unit tests + build | +| CI (Gitea Actions) | Automatic on push | Same + integration tests + contract tests | +| Deploy (Coolify) | Automatic after CI | Docker build + health check | + +Local checks catch 90% of CI failures in seconds. CI is the safety net, not the first line of defense. + +--- + +## Failures That Were Caused by Skipping Pre-Push Checks + +- `ChatFAB.tsx`: `const textLang` inside fetch object literal — caught by `tsc --noEmit` and `npm run build` +- `nodemailer` webpack error: server-only import in client component — caught by `npm run build` +- `jose` Edge Runtime error: full package import — caught by `npm run build` +- `main.py` `` tags spoken: missing `import re` — caught by `python -c "import main"` + +These all caused a broken deploy. Each would have been caught in <60 seconds locally. diff --git a/pitch-deck/components/ChatFAB.tsx b/pitch-deck/components/ChatFAB.tsx index 7e844b8..c2a6328 100644 --- a/pitch-deck/components/ChatFAB.tsx +++ b/pitch-deck/components/ChatFAB.tsx @@ -154,12 +154,10 @@ export default function ChatFAB({ ttsAbortRef.current = controller try { + const textLang = /[äöüÄÖÜß]|(?:^|\s)(?:das|die|der|und|ist|wir|ein|für|mit|auf|von|den|des)\s/i.test(cleanText) ? 'de' : lang const res = await fetch('/api/presenter/tts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - // Detect response language from text content (not UI language) - // German text typically contains umlauts, ß, or common German words - const textLang = /[äöüÄÖÜß]|(?:^|\s)(?:das|die|der|und|ist|wir|ein|für|mit|auf|von|den|des)\s/i.test(cleanText) ? 'de' : lang body: JSON.stringify({ text: cleanText, language: textLang }), signal: controller.signal, })