Set up IdeaForge project structure with documentation, architecture spec, and development guidelines for both backend (Python) and frontend (Flutter).
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.6+ / Flutter 3.27+
- Architecture: Feature-First with Riverpod
- State Management: Riverpod 3.1+ (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