This is the initial commit for the CODEX_ADK project, a full-stack AI agent management platform featuring: BACKEND (ASP.NET Core 8.0): - CQRS architecture with 6 commands and 7 queries - 16 API endpoints (all working and tested) - PostgreSQL database with 5 entities - AES-256 encryption for API keys - FluentValidation on all commands - Rate limiting and CORS configured - OpenAPI/Swagger documentation - Docker Compose setup (PostgreSQL + Ollama) FRONTEND (Flutter 3.x): - Dark theme with Svrnty branding - Collapsible sidebar navigation - CQRS API client with Result<T> error handling - Type-safe endpoints from OpenAPI schema - Multi-platform support (Web, iOS, Android, macOS, Linux, Windows) DOCUMENTATION: - Comprehensive API reference - Architecture documentation - Development guidelines for Claude Code - API integration guides - context-claude.md project overview Status: Backend ready (Grade A-), Frontend integration pending 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
416 lines
10 KiB
Markdown
416 lines
10 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
Svrnty Console is a Flutter-based management console for the Svrnty AI platform. It communicates with a C# CQRS backend using a type-safe, OpenAPI-driven API contract system.
|
|
|
|
**Tech Stack:**
|
|
- Flutter 3.x / Dart 3.9.2+
|
|
- CQRS API pattern (Command Query Responsibility Segregation)
|
|
- OpenAPI 3.0.1 contract-first architecture
|
|
- Custom theme: Crimson Red (#C44D58), Slate Blue (#475C6C)
|
|
- Fonts: Montserrat (UI), IBM Plex Mono (code/technical)
|
|
|
|
---
|
|
|
|
## Essential Commands
|
|
|
|
### Development
|
|
```bash
|
|
# Install dependencies
|
|
flutter pub get
|
|
|
|
# Run the application
|
|
flutter run
|
|
|
|
# Run on specific platform
|
|
flutter run -d macos
|
|
flutter run -d chrome
|
|
```
|
|
|
|
### Testing & Quality
|
|
```bash
|
|
# Run tests
|
|
flutter test
|
|
|
|
# Run static analysis
|
|
flutter analyze
|
|
|
|
# Verify API type safety (custom script)
|
|
./scripts/verify_api_types.sh
|
|
```
|
|
|
|
### API Contract Updates
|
|
```bash
|
|
# After backend updates openapi.json:
|
|
cp ../backend/docs/openapi.json ./api-schema.json
|
|
./scripts/update_api_client.sh
|
|
|
|
# Or run code generation directly:
|
|
flutter pub run build_runner build --delete-conflicting-outputs
|
|
|
|
# Clean build (if generation fails):
|
|
flutter clean
|
|
flutter pub get
|
|
flutter pub run build_runner build --delete-conflicting-outputs
|
|
```
|
|
|
|
### Build
|
|
```bash
|
|
# Build for production
|
|
flutter build macos
|
|
flutter build web
|
|
flutter build ios
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture Principles
|
|
|
|
### 1. OpenAPI-Driven API Contract
|
|
|
|
The backend and frontend share a single source of truth: `api-schema.json` (OpenAPI specification).
|
|
|
|
**Flow:**
|
|
1. Backend exports `docs/openapi.json` from C# controllers + XML docs
|
|
2. Frontend copies to `api-schema.json`
|
|
3. Code generation creates Dart types from contract
|
|
4. Frontend creates endpoint extensions using generated types
|
|
|
|
**Key Files:**
|
|
- `api-schema.json` - OpenAPI contract (copy from backend)
|
|
- `lib/api/client.dart` - CQRS client implementation
|
|
- `lib/api/types.dart` - Core types (Result, Serializable, errors)
|
|
- `lib/api/endpoints/` - Type-safe endpoint extensions
|
|
- `lib/api/generated/` - Auto-generated code (git-ignored)
|
|
|
|
### 2. CQRS Pattern
|
|
|
|
All backend endpoints follow CQRS with three operation types:
|
|
|
|
**Queries (Read):**
|
|
```dart
|
|
final result = await client.executeQuery<UserDto>(
|
|
endpoint: 'users/123',
|
|
query: GetUserQuery(userId: '123'),
|
|
fromJson: UserDto.fromJson,
|
|
);
|
|
```
|
|
|
|
**Commands (Write):**
|
|
```dart
|
|
final result = await client.executeCommand(
|
|
endpoint: 'createUser',
|
|
command: CreateUserCommand(name: 'John', email: 'john@example.com'),
|
|
);
|
|
```
|
|
|
|
**Paginated Queries (Lists):**
|
|
```dart
|
|
final result = await client.executePaginatedQuery<UserDto>(
|
|
endpoint: 'users',
|
|
query: ListUsersQuery(),
|
|
itemFromJson: UserDto.fromJson,
|
|
page: 1,
|
|
pageSize: 20,
|
|
filters: [FilterCriteria(field: 'status', operator: FilterOperator.equals, value: 'active')],
|
|
sorting: [SortCriteria(field: 'createdAt', direction: SortDirection.descending)],
|
|
);
|
|
```
|
|
|
|
**Important:** ALL CQRS endpoints use JSON body via POST, even empty queries send `{}`.
|
|
|
|
### 3. Functional Error Handling
|
|
|
|
Never use try-catch for API calls. Use the `Result<T>` type:
|
|
|
|
```dart
|
|
// Pattern matching
|
|
final result = await client.getUser('123');
|
|
|
|
result.when(
|
|
success: (user) => print('Hello ${user.name}'),
|
|
error: (error) => print('Error: ${error.message}'),
|
|
);
|
|
|
|
// Or switch expression
|
|
final message = switch (result) {
|
|
ApiSuccess(value: final user) => 'Hello ${user.name}',
|
|
ApiError(error: final err) => 'Error: ${err.message}',
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Mandatory Coding Standards
|
|
|
|
### Strict Typing - NO EXCEPTIONS
|
|
|
|
See `.claude-docs/strict-typing.md` for complete requirements.
|
|
|
|
**Core Rules:**
|
|
1. Every variable must have explicit type annotation
|
|
2. Every function parameter must be typed
|
|
3. Every function return value must be typed
|
|
4. **NEVER** use `dynamic` (Dart's version of `any`)
|
|
5. **NEVER** use untyped `var` declarations
|
|
6. Use proper generics, interfaces, and type unions
|
|
|
|
**Examples:**
|
|
|
|
❌ FORBIDDEN:
|
|
```dart
|
|
dynamic value = getValue();
|
|
void handleData(var data) { ... }
|
|
```
|
|
|
|
✅ REQUIRED:
|
|
```dart
|
|
UserData value = getValue();
|
|
void handleData(RequestData data) { ... }
|
|
```
|
|
|
|
### Serializable Interface
|
|
|
|
All queries, commands, and DTOs must implement:
|
|
|
|
```dart
|
|
abstract interface class Serializable {
|
|
Map<String, Object?> toJson();
|
|
}
|
|
|
|
// Example:
|
|
class GetUserQuery implements Serializable {
|
|
final String userId;
|
|
const GetUserQuery({required this.userId});
|
|
|
|
@override
|
|
Map<String, Object?> toJson() => {'userId': userId};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Response Protocol
|
|
|
|
See `.claude-docs/response-protocol.md` for complete protocol.
|
|
|
|
**All responses must end with structured choices:**
|
|
|
|
**Binary Choice:**
|
|
```
|
|
(always read claude.md to keep context between interactions)
|
|
|
|
(Y) Yes — [brief action summary]
|
|
(N) No — [brief alternative/reason]
|
|
(+) I don't understand — ask for clarification
|
|
```
|
|
|
|
**Multiple Choice:**
|
|
```
|
|
(always read claude.md to keep context between interactions)
|
|
|
|
(A) Option A — [≤8 words description]
|
|
(B) Option B — [≤8 words description]
|
|
(C) Option C — [≤8 words description]
|
|
(+) I don't understand — ask for clarification
|
|
```
|
|
|
|
When user selects `(+)`, explain the concept, then re-present options.
|
|
|
|
---
|
|
|
|
## Adding New API Endpoints
|
|
|
|
### Step 1: Backend adds endpoint
|
|
Backend team documents in C# controller with XML comments and exports `docs/openapi.json`.
|
|
|
|
### Step 2: Update contract
|
|
```bash
|
|
cp ../backend/docs/openapi.json ./api-schema.json
|
|
./scripts/update_api_client.sh
|
|
```
|
|
|
|
### Step 3: Create endpoint extension
|
|
Create file in `lib/api/endpoints/`:
|
|
|
|
```dart
|
|
import '../client.dart';
|
|
import '../types.dart';
|
|
|
|
extension UserEndpoint on CqrsApiClient {
|
|
Future<Result<UserDto>> getUser(String userId) async {
|
|
return executeQuery<UserDto>(
|
|
endpoint: 'users/$userId',
|
|
query: GetUserQuery(userId: userId),
|
|
fromJson: UserDto.fromJson,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Define query (matches backend contract)
|
|
class GetUserQuery implements Serializable {
|
|
final String userId;
|
|
const GetUserQuery({required this.userId});
|
|
|
|
@override
|
|
Map<String, Object?> toJson() => {'userId': userId};
|
|
}
|
|
|
|
// Define DTO (from OpenAPI schema)
|
|
class UserDto {
|
|
final String id;
|
|
final String name;
|
|
final String email;
|
|
|
|
const UserDto({required this.id, required this.name, required this.email});
|
|
|
|
factory UserDto.fromJson(Map<String, Object?> json) => UserDto(
|
|
id: json['id'] as String,
|
|
name: json['name'] as String,
|
|
email: json['email'] as String,
|
|
);
|
|
}
|
|
```
|
|
|
|
### Step 4: Export from api.dart
|
|
Add to `lib/api/api.dart`:
|
|
```dart
|
|
export 'endpoints/user_endpoint.dart';
|
|
```
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
### API Client Setup
|
|
|
|
**Development (localhost):**
|
|
```dart
|
|
final client = CqrsApiClient(
|
|
config: ApiClientConfig.development, // http://localhost:5246
|
|
);
|
|
```
|
|
|
|
**Android Emulator:**
|
|
```dart
|
|
final client = CqrsApiClient(
|
|
config: ApiClientConfig(
|
|
baseUrl: 'http://10.0.2.2:5246', // Special emulator IP
|
|
timeout: Duration(seconds: 30),
|
|
),
|
|
);
|
|
```
|
|
|
|
**Production:**
|
|
```dart
|
|
final client = CqrsApiClient(
|
|
config: ApiClientConfig(
|
|
baseUrl: 'https://api.svrnty.com',
|
|
timeout: Duration(seconds: 30),
|
|
defaultHeaders: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'Authorization': 'Bearer $accessToken',
|
|
},
|
|
),
|
|
);
|
|
```
|
|
|
|
Always call `client.dispose()` when done.
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Error Types
|
|
- `ApiErrorType.network` - No internet/DNS failure
|
|
- `ApiErrorType.http` - 4xx/5xx responses
|
|
- `ApiErrorType.serialization` - JSON parsing failed
|
|
- `ApiErrorType.timeout` - Request took too long
|
|
- `ApiErrorType.validation` - Backend validation error (422)
|
|
- `ApiErrorType.unknown` - Unexpected errors
|
|
|
|
### Handling Patterns
|
|
```dart
|
|
result.when(
|
|
success: (data) { /* handle success */ },
|
|
error: (error) {
|
|
switch (error.type) {
|
|
case ApiErrorType.network:
|
|
showSnackbar('No internet connection');
|
|
case ApiErrorType.timeout:
|
|
showSnackbar('Request timed out');
|
|
case ApiErrorType.validation:
|
|
showValidationErrors(error.details);
|
|
case ApiErrorType.http:
|
|
if (error.statusCode == 401) navigateToLogin();
|
|
else showSnackbar('Server error: ${error.message}');
|
|
default:
|
|
showSnackbar('Unexpected error: ${error.message}');
|
|
}
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Important Workflows
|
|
|
|
### When Backend Changes API Contract
|
|
|
|
1. Backend team notifies: "Updated API - added X endpoint"
|
|
2. Check backend CHANGELOG: `cat ../backend/docs/CHANGELOG.md`
|
|
3. Update contract: `cp ../backend/docs/openapi.json ./api-schema.json`
|
|
4. Regenerate: `./scripts/update_api_client.sh`
|
|
5. Add endpoint extension if needed (see "Adding New API Endpoints")
|
|
6. Run tests: `flutter test`
|
|
7. Verify types: `./scripts/verify_api_types.sh`
|
|
8. Commit: `git commit -m "feat: Add X endpoint integration"`
|
|
|
|
### Troubleshooting
|
|
|
|
**Code generation fails:**
|
|
```bash
|
|
flutter clean
|
|
flutter pub get
|
|
flutter pub run build_runner build --delete-conflicting-outputs
|
|
```
|
|
|
|
**Type errors after regenerating:**
|
|
Backend made breaking changes. Check `../backend/docs/CHANGELOG.md` and update code accordingly.
|
|
|
|
**Network error on device:**
|
|
- iOS/Real device: Use actual IP (e.g., `http://192.168.1.100:5246`)
|
|
- Android emulator: Use `http://10.0.2.2:5246`
|
|
|
|
**JSON parsing error:**
|
|
1. Verify `api-schema.json` matches backend's `docs/openapi.json`
|
|
2. Check DTO `fromJson` matches OpenAPI schema
|
|
3. Verify backend returned correct content-type
|
|
|
|
---
|
|
|
|
## Additional Documentation
|
|
|
|
- **API Integration Guide:** `README_API.md` (comprehensive API documentation)
|
|
- **Strict Typing Rules:** `.claude-docs/strict-typing.md` (mandatory)
|
|
- **Response Protocol:** `.claude-docs/response-protocol.md` (mandatory)
|
|
- **Backend Docs:** `../backend/docs/` (architecture, changelog)
|
|
- **OpenAPI Contract:** `api-schema.json` (source of truth)
|
|
|
|
---
|
|
|
|
## Key Reminders
|
|
|
|
1. **OpenAPI is Source of Truth** - Always regenerate from `api-schema.json`
|
|
2. **CQRS Pattern** - All endpoints use POST with JSON body (even empty `{}`)
|
|
3. **Type Safety** - No `dynamic` types, use `Serializable` interface
|
|
4. **Functional Errors** - Use `Result<T>`, not try-catch
|
|
5. **Monitor Backend CHANGELOG** - Breaking changes documented there
|
|
6. **Test Everything** - Unit tests + integration tests
|
|
7. **Follow Response Protocol** - All responses end with structured choices
|
|
8. **Strict Typing** - Explicit types everywhere, no exceptions
|