install
source Β· Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code Β· Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/feature-first" ~/.claude/skills/majiayu000-claude-skill-registry-feature-first && rm -rf "$T"
manifest:
skills/data/feature-first/SKILL.mdsource content
π¨ Skill: Feature-First Architecture
π Metadata
| Atributo | Valor |
|---|---|
| ID | |
| Nivel | π‘ Intermedio |
| VersiΓ³n | 1.0.0 |
| Keywords | , , |
| Referencia | Feature-First Architecture Guide |
π Keywords para InvocaciΓ³n
Usa cualquiera de estos keywords en tus prompts para invocar este skill:
feature-firstfeature-architecturefeature-driven@skill:feature-first
Ejemplos de Prompts
Crea una app con feature-first architecture
Organiza el proyecto con estructura feature-first
@skill:feature-first - Estructura la app por features
π DescripciΓ³n
β οΈ IMPORTANTE: Todos los comandos de este skill deben ejecutarse desde la raΓz del proyecto (donde existe el directorio
mobile/). El skill incluye verificaciones para asegurar que se estΓ‘ en el directorio correcto antes de ejecutar cualquier comando.
Feature-First Architecture organiza el cΓ³digo por features en lugar de por capas tΓ©cnicas. Cada feature contiene todo lo necesario (UI, lΓ³gica, datos) en una carpeta auto-contenida, facilitando la navegaciΓ³n y el mantenimiento del cΓ³digo.
β CuΓ‘ndo Usar Este Skill
- Proyectos medianos a grandes con mΓΊltiples features
- Equipos que trabajan en features especΓficas
- Necesitas navegaciΓ³n rΓ‘pida en el cΓ³digo
- Quieres features auto-contenidas y cohesivas
- Prefieres organizaciΓ³n por dominio de negocio
- Necesitas escalar la app agregando features
- Quieres reducir conflictos de merge en el equipo
β CuΓ‘ndo NO Usar Este Skill
- Proyectos muy pequeΓ±os (1-2 pantallas)
- Aplicaciones con pocas features
- Prefieres organizaciΓ³n por capas tΓ©cnicas (Data/Domain/Presentation)
ποΈ Estructura del Proyecto
lib/ βββ core/ β βββ constants/ β β βββ app_constants.dart β β βββ api_endpoints.dart β βββ theme/ β β βββ app_theme.dart β β βββ app_colors.dart β β βββ app_text_styles.dart β βββ widgets/ β β βββ buttons/ β β β βββ primary_button.dart β β β βββ secondary_button.dart β β βββ inputs/ β β β βββ text_field.dart β β β βββ search_field.dart β β βββ loading/ β β βββ loading_indicator.dart β βββ router/ β β βββ app_router.dart β β βββ routes.dart β βββ services/ β β βββ api_service.dart β β βββ storage_service.dart β β βββ analytics_service.dart β βββ utils/ β β βββ validators.dart β β βββ formatters.dart β β βββ extensions/ β β βββ string_extensions.dart β β βββ date_extensions.dart β β βββ context_extensions.dart β βββ error/ β βββ failures.dart β βββ exceptions.dart β βββ features/ β βββ authentication/ β β βββ data/ β β β βββ datasources/ β β β β βββ auth_local_datasource.dart β β β β βββ auth_remote_datasource.dart β β β βββ models/ β β β β βββ user_model.dart β β β β βββ token_model.dart β β β βββ repositories/ β β β βββ auth_repository_impl.dart β β βββ domain/ β β β βββ entities/ β β β β βββ user.dart β β β βββ repositories/ β β β β βββ auth_repository.dart β β β βββ usecases/ β β β βββ login_usecase.dart β β β βββ register_usecase.dart β β β βββ logout_usecase.dart β β β βββ get_user_usecase.dart β β βββ presentation/ β β β βββ bloc/ β β β β βββ auth_bloc.dart β β β β βββ auth_event.dart β β β β βββ auth_state.dart β β β β βββ login/ β β β β βββ login_cubit.dart β β β β βββ login_state.dart β β β βββ screens/ β β β β βββ login_screen.dart β β β β βββ register_screen.dart β β β β βββ forgot_password_screen.dart β β β βββ widgets/ β β β βββ login_form.dart β β β βββ register_form.dart β β β βββ social_login_buttons.dart β β βββ authentication.dart // Barrel file β β β βββ products/ β β βββ data/ β β β βββ datasources/ β β β β βββ products_local_datasource.dart β β β β βββ products_remote_datasource.dart β β β βββ models/ β β β β βββ product_model.dart β β β β βββ category_model.dart β β β βββ repositories/ β β β βββ products_repository_impl.dart β β βββ domain/ β β β βββ entities/ β β β β βββ product.dart β β β β βββ category.dart β β β βββ repositories/ β β β β βββ products_repository.dart β β β βββ usecases/ β β β βββ get_products_usecase.dart β β β βββ get_product_detail_usecase.dart β β β βββ search_products_usecase.dart β β β βββ filter_products_usecase.dart β β βββ presentation/ β β β βββ bloc/ β β β β βββ products_bloc.dart β β β β βββ products_event.dart β β β β βββ products_state.dart β β β β βββ product_detail/ β β β β βββ product_detail_cubit.dart β β β β βββ product_detail_state.dart β β β βββ screens/ β β β β βββ products_screen.dart β β β β βββ product_detail_screen.dart β β β β βββ search_screen.dart β β β βββ widgets/ β β β βββ product_card.dart β β β βββ product_grid.dart β β β βββ category_filter.dart β β β βββ price_filter.dart β β βββ products.dart // Barrel file β β β βββ cart/ β β βββ data/ β β β βββ datasources/ β β β β βββ cart_local_datasource.dart β β β βββ models/ β β β β βββ cart_item_model.dart β β β βββ repositories/ β β β βββ cart_repository_impl.dart β β βββ domain/ β β β βββ entities/ β β β β βββ cart_item.dart β β β βββ repositories/ β β β β βββ cart_repository.dart β β β βββ usecases/ β β β βββ add_to_cart_usecase.dart β β β βββ remove_from_cart_usecase.dart β β β βββ update_quantity_usecase.dart β β β βββ get_cart_items_usecase.dart β β βββ presentation/ β β β βββ bloc/ β β β β βββ cart_bloc.dart β β β β βββ cart_event.dart β β β β βββ cart_state.dart β β β βββ screens/ β β β β βββ cart_screen.dart β β β βββ widgets/ β β β βββ cart_item_card.dart β β β βββ cart_summary.dart β β β βββ empty_cart.dart β β βββ cart.dart // Barrel file β β β βββ orders/ β β βββ data/ β β βββ domain/ β β βββ presentation/ β β βββ orders.dart β β β βββ profile/ β β βββ data/ β β βββ domain/ β β βββ presentation/ β β βββ profile.dart β β β βββ settings/ β βββ data/ β βββ domain/ β βββ presentation/ β βββ settings.dart β βββ main.dart
π¦ Dependencias Requeridas
dependencies: flutter: sdk: flutter # State Management flutter_bloc: ^8.1.3 equatable: ^2.0.5 # Navigation go_router: ^12.1.3 # Dependency Injection get_it: ^7.6.4 injectable: ^2.3.2 # Networking dio: ^5.4.0 retrofit: ^4.0.3 # Local Storage hive: ^2.2.3 hive_flutter: ^1.1.0 # Utils dartz: ^0.10.1 freezed_annotation: ^2.4.1 json_annotation: ^4.8.1 dev_dependencies: # Code Generation build_runner: ^2.4.6 freezed: ^2.4.5 json_serializable: ^6.7.1 injectable_generator: ^2.4.1 retrofit_generator: ^8.0.6 hive_generator: ^2.0.1 # Testing flutter_test: sdk: flutter bloc_test: ^9.1.4 mocktail: ^1.0.1
π» ImplementaciΓ³n
1. Core - ConfiguraciΓ³n de Router
// lib/core/router/app_router.dart import 'package:go_router/go_router.dart'; import 'package:flutter/material.dart'; import '../../features/authentication/authentication.dart'; import '../../features/products/products.dart'; import '../../features/cart/cart.dart'; import '../../features/orders/orders.dart'; import '../../features/profile/profile.dart'; final appRouter = GoRouter( initialLocation: '/login', routes: [ // Authentication Routes GoRoute( path: '/login', name: 'login', builder: (context, state) => const LoginScreen(), ), GoRoute( path: '/register', name: 'register', builder: (context, state) => const RegisterScreen(), ), // Main App with Bottom Navigation ShellRoute( builder: (context, state, child) { return MainScaffold(child: child); }, routes: [ // Products Routes GoRoute( path: '/products', name: 'products', builder: (context, state) => const ProductsScreen(), routes: [ GoRoute( path: ':id', name: 'product-detail', builder: (context, state) { final productId = state.pathParameters['id']!; return ProductDetailScreen(productId: productId); }, ), ], ), // Cart Routes GoRoute( path: '/cart', name: 'cart', builder: (context, state) => const CartScreen(), ), // Orders Routes GoRoute( path: '/orders', name: 'orders', builder: (context, state) => const OrdersScreen(), routes: [ GoRoute( path: ':id', name: 'order-detail', builder: (context, state) { final orderId = state.pathParameters['id']!; return OrderDetailScreen(orderId: orderId); }, ), ], ), // Profile Routes GoRoute( path: '/profile', name: 'profile', builder: (context, state) => const ProfileScreen(), ), ], ), ], redirect: (context, state) { // Implementar lΓ³gica de autenticaciΓ³n aquΓ // final isAuthenticated = ... // if (!isAuthenticated && state.location != '/login') { // return '/login'; // } return null; }, );
2. Dependency Injection
// lib/core/di/injection.dart import 'package:get_it/get_it.dart'; import 'package:injectable/injectable.dart'; import 'injection.config.dart'; final getIt = GetIt.instance; @InjectableInit() Future<void> configureDependencies() async { await getIt.init(); }
// lib/core/di/injection.config.dart (generado) // Ejecutar desde la raΓz del proyecto: // cd mobile && dart run build_runner build --delete-conflicting-outputs && cd ..
3. Feature: Authentication
Domain Layer
// lib/features/authentication/domain/entities/user.dart import 'package:equatable/equatable.dart'; class User extends Equatable { final String id; final String email; final String name; final String? avatar; const User({ required this.id, required this.email, required this.name, this.avatar, }); @override List<Object?> get props => [id, email, name, avatar]; }
// lib/features/authentication/domain/repositories/auth_repository.dart import 'package:dartz/dartz.dart'; import '../../../../core/error/failures.dart'; import '../entities/user.dart'; abstract class AuthRepository { Future<Either<Failure, User>> login({ required String email, required String password, }); Future<Either<Failure, User>> register({ required String email, required String password, required String name, }); Future<Either<Failure, void>> logout(); Future<Either<Failure, User>> getCurrentUser(); }
// lib/features/authentication/domain/usecases/login_usecase.dart import 'package:dartz/dartz.dart'; import 'package:injectable/injectable.dart'; import '../../../../core/error/failures.dart'; import '../entities/user.dart'; import '../repositories/auth_repository.dart'; @injectable class LoginUseCase { final AuthRepository repository; LoginUseCase(this.repository); Future<Either<Failure, User>> call({ required String email, required String password, }) async { return await repository.login(email: email, password: password); } }
Data Layer
// lib/features/authentication/data/models/user_model.dart import 'package:freezed_annotation/freezed_annotation.dart'; import '../../domain/entities/user.dart'; part 'user_model.freezed.dart'; part 'user_model.g.dart'; @freezed class UserModel with _$UserModel { const UserModel._(); const factory UserModel({ required String id, required String email, required String name, String? avatar, }) = _UserModel; factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json); // Convert to domain entity User toEntity() { return User( id: id, email: email, name: name, avatar: avatar, ); } // Convert from domain entity factory UserModel.fromEntity(User user) { return UserModel( id: user.id, email: user.email, name: user.name, avatar: user.avatar, ); } }
// lib/features/authentication/data/datasources/auth_remote_datasource.dart import 'package:dio/dio.dart'; import 'package:injectable/injectable.dart'; import '../models/user_model.dart'; abstract class AuthRemoteDataSource { Future<UserModel> login({required String email, required String password}); Future<UserModel> register({required String email, required String password, required String name}); Future<void> logout(); Future<UserModel> getCurrentUser(); } @LazySingleton(as: AuthRemoteDataSource) class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { final Dio dio; AuthRemoteDataSourceImpl(this.dio); @override Future<UserModel> login({ required String email, required String password, }) async { try { final response = await dio.post( '/auth/login', data: { 'email': email, 'password': password, }, ); return UserModel.fromJson(response.data['user']); } catch (e) { throw Exception('Login failed: $e'); } } @override Future<UserModel> register({ required String email, required String password, required String name, }) async { try { final response = await dio.post( '/auth/register', data: { 'email': email, 'password': password, 'name': name, }, ); return UserModel.fromJson(response.data['user']); } catch (e) { throw Exception('Registration failed: $e'); } } @override Future<void> logout() async { try { await dio.post('/auth/logout'); } catch (e) { throw Exception('Logout failed: $e'); } } @override Future<UserModel> getCurrentUser() async { try { final response = await dio.get('/auth/user'); return UserModel.fromJson(response.data['user']); } catch (e) { throw Exception('Get current user failed: $e'); } } }
// lib/features/authentication/data/repositories/auth_repository_impl.dart import 'package:dartz/dartz.dart'; import 'package:injectable/injectable.dart'; import '../../../../core/error/failures.dart'; import '../../domain/entities/user.dart'; import '../../domain/repositories/auth_repository.dart'; import '../datasources/auth_remote_datasource.dart'; @LazySingleton(as: AuthRepository) class AuthRepositoryImpl implements AuthRepository { final AuthRemoteDataSource remoteDataSource; AuthRepositoryImpl(this.remoteDataSource); @override Future<Either<Failure, User>> login({ required String email, required String password, }) async { try { final userModel = await remoteDataSource.login( email: email, password: password, ); return Right(userModel.toEntity()); } catch (e) { return Left(ServerFailure(e.toString())); } } @override Future<Either<Failure, User>> register({ required String email, required String password, required String name, }) async { try { final userModel = await remoteDataSource.register( email: email, password: password, name: name, ); return Right(userModel.toEntity()); } catch (e) { return Left(ServerFailure(e.toString())); } } @override Future<Either<Failure, void>> logout() async { try { await remoteDataSource.logout(); return const Right(null); } catch (e) { return Left(ServerFailure(e.toString())); } } @override Future<Either<Failure, User>> getCurrentUser() async { try { final userModel = await remoteDataSource.getCurrentUser(); return Right(userModel.toEntity()); } catch (e) { return Left(ServerFailure(e.toString())); } } }
Presentation Layer
// lib/features/authentication/presentation/bloc/login/login_cubit.dart import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:injectable/injectable.dart'; import '../../../domain/usecases/login_usecase.dart'; part 'login_state.dart'; part 'login_cubit.freezed.dart'; @injectable class LoginCubit extends Cubit<LoginState> { final LoginUseCase loginUseCase; LoginCubit(this.loginUseCase) : super(const LoginState.initial()); Future<void> login({ required String email, required String password, }) async { emit(const LoginState.loading()); final result = await loginUseCase(email: email, password: password); result.fold( (failure) => emit(LoginState.error(failure.message)), (user) => emit(LoginState.success(user)), ); } }
// lib/features/authentication/presentation/bloc/login/login_state.dart part of 'login_cubit.dart'; @freezed class LoginState with _$LoginState { const factory LoginState.initial() = LoginInitial; const factory LoginState.loading() = LoginLoading; const factory LoginState.success(User user) = LoginSuccess; const factory LoginState.error(String message) = LoginError; }
// lib/features/authentication/presentation/screens/login_screen.dart import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/di/injection.dart'; import '../../../../core/widgets/buttons/primary_button.dart'; import '../../../../core/theme/app_colors.dart'; import '../bloc/login/login_cubit.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State<LoginScreen> createState() => _LoginScreenState(); } class _LoginScreenState extends State<LoginScreen> { final _formKey = GlobalKey<FormState>(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); @override void dispose() { _emailController.dispose(); _passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocProvider( create: (context) => getIt<LoginCubit>(), child: Scaffold( appBar: AppBar( title: const Text('Login'), ), body: BlocConsumer<LoginCubit, LoginState>( listener: (context, state) { state.maybeWhen( success: (user) { context.go('/products'); }, error: (message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: AppColors.error, ), ); }, orElse: () {}, ); }, builder: (context, state) { final isLoading = state is LoginLoading; return Padding( padding: const EdgeInsets.all(24.0), child: Form( key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextFormField( controller: _emailController, decoration: const InputDecoration( labelText: 'Email', prefixIcon: Icon(Icons.email), ), keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your email'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _passwordController, decoration: const InputDecoration( labelText: 'Password', prefixIcon: Icon(Icons.lock), ), obscureText: true, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your password'; } return null; }, ), const SizedBox(height: 24), SizedBox( width: double.infinity, child: PrimaryButton( text: 'Login', isLoading: isLoading, onPressed: () { if (_formKey.currentState!.validate()) { context.read<LoginCubit>().login( email: _emailController.text, password: _passwordController.text, ); } }, ), ), const SizedBox(height: 16), TextButton( onPressed: () => context.push('/register'), child: const Text('Don\'t have an account? Register'), ), ], ), ), ); }, ), ), ); } }
Barrel File
// lib/features/authentication/authentication.dart // Domain export 'domain/entities/user.dart'; export 'domain/repositories/auth_repository.dart'; export 'domain/usecases/login_usecase.dart'; export 'domain/usecases/register_usecase.dart'; export 'domain/usecases/logout_usecase.dart'; export 'domain/usecases/get_user_usecase.dart'; // Presentation export 'presentation/screens/login_screen.dart'; export 'presentation/screens/register_screen.dart'; export 'presentation/bloc/auth_bloc.dart'; export 'presentation/bloc/login/login_cubit.dart';
4. Main Setup
// lib/main.dart import 'package:flutter/material.dart'; import 'core/di/injection.dart'; import 'core/router/app_router.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Configure dependency injection await configureDependencies(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Feature-First App', theme: ThemeData( primarySwatch: Colors.blue, useMaterial3: true, ), routerConfig: appRouter, ); } }
π― Mejores PrΓ‘cticas
1. OrganizaciΓ³n por Feature
β DO:
features/ authentication/ data/ domain/ presentation/ authentication.dart
β DON'T:
data/ authentication/ domain/ authentication/ presentation/ authentication/
2. Barrel Files
β DO:
// lib/features/products/products.dart export 'domain/entities/product.dart'; export 'presentation/screens/products_screen.dart'; // Solo exporta APIs pΓΊblicas
β DON'T:
// No expongas implementaciones internas export 'data/datasources/products_remote_datasource.dart'; // β export 'data/models/product_model.dart'; // β
3. Dependencias entre Features
β DO:
// Usa core para comunicaciΓ³n entre features import 'package:app/core/services/event_bus.dart'; // O pasa datos a travΓ©s de navigation context.push('/cart', extra: product);
β DON'T:
// No importes directamente desde otras features import '../../products/domain/entities/product.dart'; // β
4. Testing por Feature
β DO:
features/ authentication/ test/ unit/ widget/ integration/
π Recursos Adicionales
π Skills Relacionados
- Clean Architecture - Arquitectura de cada feature
- Modular Architecture - Alternativa modular
- Testing Strategy - Testing de features
VersiΓ³n: 1.0.0
Γltima actualizaciΓ³n: Diciembre 2025