Riverpod updated to 3.2+ (Dart 3.7+), deployment platform set to TBD, riverpod_generator updated to ^4.0.2.
11 KiB
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
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
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.
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.
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
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
@riverpod
class IdeasController extends _$IdeasController {
@override
Future<List<IdeaEntity>> build() async {
return ref.read(ideaRepositoryProvider).getIdeas();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => ref.read(ideaRepositoryProvider).getIdeas(),
);
}
}
Frontend — Repository Pattern
// domain/repositories/idea_repository.dart (interface)
abstract class IdeaRepository {
Future<List<IdeaEntity>> getIdeas({String? status, String? category});
Future<IdeaEntity> getIdea(int id);
Future<EvaluationResult> triggerEvaluation(int id);
}
// data/repositories/api_idea_repository.dart (implementation)
class ApiIdeaRepository implements IdeaRepository {
ApiIdeaRepository({required this.client});
final Dio client;
@override
Future<List<IdeaEntity>> 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):
- Standard library
- Third-party packages
- Local imports
Always use absolute imports from package root.
Dart
All imports in one section, sorted alphabetically, no blank lines between:
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+):
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
@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
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 issuesuv run ruff format --check .returns 0 changesuv run mypy src/passesuv run pytestpasses- 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 analyzereturns 0 issuesdart format .returns 0 changesflutter testpasses- 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): descriptionformat