# CLAUDE.md Guidelines for working on IdeaForge. ## Project Overview IdeaForge is a full-stack idea management platform: a Flutter Web/Mobile client backed by a Python API with integrated AI evaluation. The backend extends the existing idea-manager project with Claude API integration and a public demo mode. ## Project Stack ### Backend (Python) - **Language:** Python 3.11+ - **Package Manager:** uv - **API Framework:** FastAPI 0.115+ with uvicorn - **ORM:** SQLAlchemy 2.0+ (async) with aiosqlite - **Database:** SQLite (file-based) - **Validation:** Pydantic 2.10+, pydantic-settings - **Logging:** structlog (JSON structured logs) - **Linting:** ruff (lint + format) - **Type Checking:** mypy (strict) - **Testing:** pytest, pytest-asyncio, pytest-cov, httpx - **AI Integration:** Anthropic Python SDK (Claude API) ### Frontend (Flutter) - **Language:** Dart 3.7+ / Flutter 3.27+ - **Architecture:** Feature-First with Riverpod - **State Management:** Riverpod 3.2+ (generator syntax) - **Navigation:** GoRouter - **HTTP Client:** dio - **Charts:** fl_chart (radar/spider charts for metric scores) - **Testing:** flutter_test, mocktail - **Linting:** flutter analyze (zero issues including info-level) ## Commands ### Backend ```bash cd backend/ uv sync # Install dependencies uv run alembic upgrade head # Run migrations uv run ideaforge serve # Start API server uv run pytest --cov=src/ideaforge # Tests (target: ≥80%) uv run ruff check . # Linting (must return ZERO issues) uv run ruff format --check . # Format check (ZERO changes) uv run mypy src/ # Type checking (must pass) ``` ### Frontend ```bash cd frontend/ flutter pub get # Install dependencies dart run build_runner build --delete-conflicting-outputs # Code generation flutter test --coverage # Tests (target: ≥85%) flutter analyze # Must return ZERO issues dart format . # Must produce ZERO changes flutter build web # Web build ``` ## Architecture ### Directory Structure ``` ideaforge/ ├── backend/ # Python API │ ├── src/ideaforge/ │ │ ├── __init__.py │ │ ├── main.py # FastAPI app entry point │ │ ├── config.py # Pydantic settings │ │ ├── database.py # DB engine, session factory │ │ ├── dependencies.py # FastAPI dependency injection │ │ ├── exceptions.py # Custom exception hierarchy │ │ ├── logging.py # structlog configuration │ │ ├── models/ # SQLAlchemy ORM models │ │ ├── schemas/ # Pydantic request/response schemas │ │ ├── services/ # Business logic layer │ │ ├── api/ # FastAPI routers │ │ └── ai/ # Claude API integration │ │ ├── client.py # Anthropic SDK wrapper │ │ ├── prompts.py # Evaluation prompt templates │ │ └── evaluator.py # AI evaluation orchestration │ ├── alembic/ # Database migrations │ ├── tests/ │ ├── pyproject.toml │ └── alembic.ini │ ├── frontend/ # Flutter app │ ├── lib/ │ │ ├── main.dart │ │ ├── src/ │ │ │ ├── core/ # Shared utilities, theme, errors │ │ │ ├── features/ │ │ │ │ ├── ideas/ # Idea listing and filtering │ │ │ │ │ ├── domain/ │ │ │ │ │ ├── data/ │ │ │ │ │ └── presentation/ │ │ │ │ ├── evaluation/ # AI evaluation and scoring │ │ │ │ │ ├── domain/ │ │ │ │ │ ├── data/ │ │ │ │ │ └── presentation/ │ │ │ │ └── dashboard/ # Overview and charts │ │ │ │ └── presentation/ │ │ │ └── shared/ # Cross-feature widgets │ │ └── app.dart # App root with providers and router │ ├── test/ │ ├── web/ │ └── pubspec.yaml │ ├── docs/ ├── readme.md ├── changelog.md └── docker-compose.yml # Local development (API + DB) ``` ### Layer Responsibilities (Backend) | Layer | Purpose | Dependencies | |-------|---------|--------------| | **models/** | SQLAlchemy ORM models | SQLAlchemy only | | **schemas/** | Pydantic request/response validation | Pydantic only | | **services/** | Business logic, orchestration | models, schemas | | **ai/** | Claude API integration, prompt management | Anthropic SDK, schemas | | **api/** | HTTP routing, request handling | services, schemas | **Critical:** Services NEVER import from api/. API depends on services, not the reverse. ### Layer Responsibilities (Frontend) | Layer | Purpose | Dependencies | |-------|---------|--------------| | **domain/** | Entities, repository interfaces | None (pure Dart) | | **data/** | DTOs, API client, repository implementations | dio, domain | | **presentation/** | Controllers, views, widgets | Riverpod, domain | **Critical:** Domain layer has zero external dependencies. Data implements domain interfaces. ## Code Patterns ### Backend — Error Handling Custom exceptions inherit from a base class. Services raise domain exceptions, API routes convert them to HTTP responses via exception handlers. ```python class IdeaForgeError(Exception): def __init__(self, message: str, detail: str | None = None): self.message = message self.detail = detail super().__init__(message) class NotFoundError(IdeaForgeError): ... class EvaluationError(IdeaForgeError): ... class AIServiceError(IdeaForgeError): ... ``` ### Backend — Logging (structlog) Every service method logs entry, success, and failure with context. ```python import structlog logger = structlog.get_logger(__name__) async def evaluate_idea(self, idea_id: int) -> Evaluation: logger.info("evaluating_idea", idea_id=idea_id) try: result = await self._run_evaluation(idea_id) logger.info("idea_evaluated", idea_id=idea_id, score=result.score) return result except Exception as e: logger.error("evaluation_failed", idea_id=idea_id, error=str(e)) raise ``` Never use `print()`. Always include context parameters. ### Backend — AI Integration ```python from anthropic import AsyncAnthropic class EvaluationService: def __init__(self, session: AsyncSession, ai_client: AsyncAnthropic): self.session = session self.ai_client = ai_client async def run_ai_evaluation(self, idea_id: int) -> list[MetricScore]: idea = await self._get_idea(idea_id) prompt = build_evaluation_prompt(idea) response = await self.ai_client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4096, messages=[{"role": "user", "content": prompt}], ) return parse_evaluation_response(response) ``` ### Frontend — Riverpod Controller ```dart @riverpod class IdeasController extends _$IdeasController { @override Future> build() async { return ref.read(ideaRepositoryProvider).getIdeas(); } Future refresh() async { state = const AsyncLoading(); state = await AsyncValue.guard( () => ref.read(ideaRepositoryProvider).getIdeas(), ); } } ``` ### Frontend — Repository Pattern ```dart // domain/repositories/idea_repository.dart (interface) abstract class IdeaRepository { Future> getIdeas({String? status, String? category}); Future getIdea(int id); Future triggerEvaluation(int id); } // data/repositories/api_idea_repository.dart (implementation) class ApiIdeaRepository implements IdeaRepository { ApiIdeaRepository({required this.client}); final Dio client; @override Future> getIdeas({String? status, String? category}) async { final response = await client.get('/api/ideas', queryParameters: { if (status != null) 'status': status, if (category != null) 'category': category, }); return (response.data as List) .map((json) => IdeaDto.fromJson(json).toEntity()) .toList(); } } ``` ## Import Ordering ### Python Use ruff import sorting (isort-compatible): 1. Standard library 2. Third-party packages 3. Local imports Always use absolute imports from package root. ### Dart All imports in one section, sorted alphabetically, no blank lines between: ```dart import 'package:flutter/material.dart'; import 'package:ideaforge/src/core/errors/failures.dart'; import 'package:ideaforge/src/features/ideas/ideas.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; ``` Always use package imports, never relative. ## Type Safety ### Python All functions must have complete type hints. Use modern syntax (Python 3.11+): ```python def process(items: list[str]) -> dict[str, int]: ... def get_user(user_id: int | None = None) -> User | None: ... ``` ### Dart Avoid `dynamic`. Prefer explicit types on public APIs. Use `required` for named parameters that must be provided. ## Testing ### Backend ```python @pytest.mark.asyncio async def test_evaluate_idea(client: AsyncClient): response = await client.post("/api/ideas/1/evaluate") assert response.status_code == 200 data = response.json() assert "scores" in data ``` ### Frontend ```dart testWidgets('shows idea list', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ideaRepositoryProvider.overrideWithValue(mockRepo)], child: const MaterialApp(home: IdeasScreen()), ), ); expect(find.text('Test Idea'), findsOneWidget); }); ``` ## Git - **Repo:** https://gitea.kschappell.com/kschappell/ideaforge - **Branch:** main - See workspace CLAUDE.md for commit format and signing requirements ## Demo Mode The deployed instance runs in demo mode: - Seeded with 15-20 curated ideas across 3-4 domains - Write operations disabled for anonymous users - AI evaluation rate-limited (prevent abuse) - Data resets periodically Configuration via environment variable: `IDEAFORGE_DEMO_MODE=true` ## Pre-Completion Checklist Before marking ANY task complete: **Backend:** - [ ] `uv run ruff check .` returns 0 issues - [ ] `uv run ruff format --check .` returns 0 changes - [ ] `uv run mypy src/` passes - [ ] `uv run pytest` passes - [ ] All new functions have complete type hints - [ ] All new service methods have structlog logging - [ ] All new exceptions inherit from `IdeaForgeError` - [ ] No `print()` statements **Frontend:** - [ ] `flutter analyze` returns 0 issues - [ ] `dart format .` returns 0 changes - [ ] `flutter test` passes - [ ] New features have at least one widget test - [ ] No AI/LLM references anywhere in the codebase **Both:** - [ ] changelog.md updated - [ ] Commit is signed - [ ] Commit message follows `type(scope): description` format