coregen-cli 3.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coregen.py +1585 -0
- coregen_cli-3.1.6.dist-info/METADATA +1211 -0
- coregen_cli-3.1.6.dist-info/RECORD +172 -0
- coregen_cli-3.1.6.dist-info/WHEEL +5 -0
- coregen_cli-3.1.6.dist-info/entry_points.txt +2 -0
- coregen_cli-3.1.6.dist-info/top_level.txt +2 -0
- generators/__init__.py +1 -0
- generators/assets/__init__.py +1 -0
- generators/assets/assets.py +30 -0
- generators/assets/main.py +4 -0
- generators/config/__init__.py +1 -0
- generators/config/analisy_options.py +42 -0
- generators/config/main.py +11 -0
- generators/config/pubspec.py +139 -0
- generators/cursor/__init__.py +3 -0
- generators/cursor/setup.py +87 -0
- generators/helpers/__init__.py +56 -0
- generators/helpers/component.py +1030 -0
- generators/helpers/config.py +255 -0
- generators/helpers/data_source.py +180 -0
- generators/helpers/domain.py +469 -0
- generators/helpers/feature.py +654 -0
- generators/helpers/navigation.py +274 -0
- generators/helpers/page.py +63 -0
- generators/helpers/project.py +33 -0
- generators/helpers/utils.py +338 -0
- generators/helpers/validation.py +364 -0
- generators/initializator.py +68 -0
- generators/main.py +81 -0
- generators/static/assets/logo.png +0 -0
- generators/static/assets/mock/auth_user.json +5 -0
- generators/static/assets/svgs/apple.svg +4 -0
- generators/static/assets/svgs/google.svg +2 -0
- generators/static/cursor/AGENTS.md.jinja +37 -0
- generators/static/cursor/agents/doc-writer.md +20 -0
- generators/static/cursor/agents/epic-orchestrator.md +28 -0
- generators/static/cursor/agents/feature-implementer.md +29 -0
- generators/static/cursor/agents/integration-wiring.md +30 -0
- generators/static/cursor/agents/layer-application.md +21 -0
- generators/static/cursor/agents/layer-guardian.md +21 -0
- generators/static/cursor/agents/layer-infrastructure.md +31 -0
- generators/static/cursor/agents/layer-model.md +24 -0
- generators/static/cursor/agents/layer-presentation.md +33 -0
- generators/static/cursor/docs/architecture/APIS_AND_INTEGRATION.md.jinja +42 -0
- generators/static/cursor/docs/architecture/CARAVAGGIO_COMPONENTS.md.jinja +317 -0
- generators/static/cursor/docs/architecture/DDD_LAYERS.md.jinja +32 -0
- generators/static/cursor/docs/architecture/FILE_TEMPLATES.md.jinja +76 -0
- generators/static/cursor/docs/architecture/MOCK_AND_REMOTE_DATA.md.jinja +87 -0
- generators/static/cursor/docs/architecture/REFERENCE_IMPLEMENTATIONS.md.jinja +48 -0
- generators/static/cursor/docs/architecture/WIDGETS_AND_CARAVAGGIO.md.jinja +76 -0
- generators/static/cursor/docs/epics/README.md +17 -0
- generators/static/cursor/lib/domain/AGENTS.md +23 -0
- generators/static/cursor/lib/features/AGENTS.md +23 -0
- generators/static/cursor/lib/widgets/AGENTS.md +21 -0
- generators/static/cursor/rules/apis-layer.mdc.jinja +21 -0
- generators/static/cursor/rules/application-layer.mdc.jinja +12 -0
- generators/static/cursor/rules/architecture-core.mdc.jinja +43 -0
- generators/static/cursor/rules/dart-conventions.mdc.jinja +14 -0
- generators/static/cursor/rules/domain-layer.mdc.jinja +24 -0
- generators/static/cursor/rules/login-context.mdc.jinja +16 -0
- generators/static/cursor/rules/presentation-layer.mdc.jinja +15 -0
- generators/static/cursor/rules/quality-gate.mdc.jinja +14 -0
- generators/static/cursor/rules/ui-caravaggio.mdc.jinja +22 -0
- generators/static/cursor/skills/caravaggio-ui/SKILL.md +138 -0
- generators/static/cursor/skills/epic-delivery/SKILL.md +74 -0
- generators/static/cursor/skills/epic-delivery/templates/epic-status.md +14 -0
- generators/static/cursor/skills/epic-delivery/templates/epic.md +36 -0
- generators/static/templates/apis/common/constants_template.jinja +48 -0
- generators/static/templates/apis/common/data_source_config_template.jinja +47 -0
- generators/static/templates/apis/core/api_injectable_module_template.jinja +14 -0
- generators/static/templates/apis/interceptors/api_logger_template.jinja +135 -0
- generators/static/templates/apis/interceptors/auth_interceptor_template.jinja +207 -0
- generators/static/templates/auth/application/auth_bloc_template.jinja +36 -0
- generators/static/templates/auth/application/auth_event_template.jinja +7 -0
- generators/static/templates/auth/application/auth_state_template.jinja +8 -0
- generators/static/templates/auth/infrastructure/auth_facade_module_template.jinja +16 -0
- generators/static/templates/auth/infrastructure/auth_facade_template.jinja +288 -0
- generators/static/templates/auth/infrastructure/mock_auth_facade_template.jinja +106 -0
- generators/static/templates/auth/model/auth_failure_template.jinja +11 -0
- generators/static/templates/auth/model/i_auth_facade_template.jinja +24 -0
- generators/static/templates/auth/model/user_template.jinja +34 -0
- generators/static/templates/auth/model/value_objects_template.jinja +54 -0
- generators/static/templates/auth/presentation/login_screen_template.jinja +24 -0
- generators/static/templates/auth/sign_in_form/application/sign_in_form_bloc_template.jinja +139 -0
- generators/static/templates/auth/sign_in_form/application/sign_in_form_event_template.jinja +15 -0
- generators/static/templates/auth/sign_in_form/application/sign_in_form_state_template.jinja +26 -0
- generators/static/templates/auth/sign_in_form/presentation/apple_form_template.jinja +16 -0
- generators/static/templates/auth/sign_in_form/presentation/email_password_form_template_INACTIVE.jinja +94 -0
- generators/static/templates/auth/sign_in_form/presentation/google_form_template.jinja +15 -0
- generators/static/templates/auth/sign_in_form/presentation/sign_in_form_template.jinja +381 -0
- generators/static/templates/component/component_bloc_template.jinja +49 -0
- generators/static/templates/component/component_event_template.jinja +7 -0
- generators/static/templates/component/component_form_bloc_template.jinja +56 -0
- generators/static/templates/component/component_form_event_template.jinja +7 -0
- generators/static/templates/component/component_form_state_template.jinja +18 -0
- generators/static/templates/component/component_form_widget_template.jinja +130 -0
- generators/static/templates/component/component_list_widget_template.jinja +95 -0
- generators/static/templates/component/component_state_template.jinja +9 -0
- generators/static/templates/component/component_widget_template.jinja +113 -0
- generators/static/templates/core/bloc/base_form_bloc_template.jinja +32 -0
- generators/static/templates/core/errors/error_localizer_template.jinja +61 -0
- generators/static/templates/core/infrastructure/base_mapper_template.jinja +21 -0
- generators/static/templates/core/infrastructure/base_repository_mixin_template.jinja +78 -0
- generators/static/templates/core/infrastructure/firebase_injectable_module_template.jinja +11 -0
- generators/static/templates/core/infrastructure/firestore_helpers_template.jinja +22 -0
- generators/static/templates/core/infrastructure/repository_error_handler_template.jinja +44 -0
- generators/static/templates/core/model/common_interfaces_template.jinja +3 -0
- generators/static/templates/core/model/entity_template.jinja +5 -0
- generators/static/templates/core/model/errors_template.jinja +15 -0
- generators/static/templates/core/model/failures_template.jinja +97 -0
- generators/static/templates/core/model/value_objects_template.jinja +135 -0
- generators/static/templates/core/model/value_validators_template.jinja +105 -0
- generators/static/templates/core/presentation/app_drawer_template.jinja +52 -0
- generators/static/templates/core/presentation/app_widget_template.jinja +41 -0
- generators/static/templates/core/presentation/bottom_nav_bar_template.jinja +32 -0
- generators/static/templates/domain/domain_mapper_template.jinja +39 -0
- generators/static/templates/domain/domain_remote_service_template.jinja +46 -0
- generators/static/templates/domain/domain_repository_template.jinja +90 -0
- generators/static/templates/domain/domain_service_module_template.jinja +23 -0
- generators/static/templates/domain/enum_template.jinja +3 -0
- generators/static/templates/domain/i_domain_service_template.jinja +18 -0
- generators/static/templates/domain/mock_domain_service_template.jinja +73 -0
- generators/static/templates/feature/feature_bloc_template.jinja +104 -0
- generators/static/templates/feature/feature_dto_template.jinja +26 -0
- generators/static/templates/feature/feature_entity_template.jinja +23 -0
- generators/static/templates/feature/feature_event_template.jinja +20 -0
- generators/static/templates/feature/feature_extensions_template.jinja +25 -0
- generators/static/templates/feature/feature_failure_template.jinja +11 -0
- generators/static/templates/feature/feature_page_template.jinja +113 -0
- generators/static/templates/feature/feature_repository_template.jinja +112 -0
- generators/static/templates/feature/feature_state_template.jinja +20 -0
- generators/static/templates/feature/i_feature_repository_template.jinja +21 -0
- generators/static/templates/feature/value_object_field_template.jinja +0 -0
- generators/static/templates/feature/value_object_template.jinja +0 -0
- generators/static/templates/feature/value_validators_template.jinja +57 -0
- generators/static/templates/home/home_screen_template.jinja +68 -0
- generators/static/templates/home/presentation/home_screen_template.jinja +52 -0
- generators/static/templates/home/screen_template.jinja +12 -0
- generators/static/templates/injection_template.jinja +8 -0
- generators/static/templates/logging/analytics_logging_template.jinja +42 -0
- generators/static/templates/logging/console_template.jinja +50 -0
- generators/static/templates/logging/logger_injectable_module_template.jinja +24 -0
- generators/static/templates/logging/logger_template.jinja +118 -0
- generators/static/templates/main_template.jinja +43 -0
- generators/static/templates/page_template.jinja +29 -0
- generators/static/templates/router_template.jinja +26 -0
- generators/static/templates/splash/presentation/splash_screen_auth_template.jinja +97 -0
- generators/static/templates/splash/presentation/splash_screen_template.jinja +51 -0
- generators/static/templates/storage/storage_repository_template.jinja +80 -0
- generators/static/templates/user_profile/infrastructure/user_profile_dto_template.jinja +27 -0
- generators/static/templates/user_profile/infrastructure/user_profile_mapper_template.jinja +43 -0
- generators/static/templates/user_profile/infrastructure/user_profile_repository_template.jinja +82 -0
- generators/static/templates/user_profile/infrastructure/user_profile_service_template.jinja +27 -0
- generators/static/templates/user_profile/model/i_user_profile_repository_template.jinja +34 -0
- generators/static/templates/user_profile/model/user_profile_failure_template.jinja +11 -0
- generators/static/templates/user_profile/model/user_profile_template.jinja +31 -0
- generators/static/templates/widgets/common/custom_scaffold_template.jinja +52 -0
- generators/static/templates/widgets/common/error_widget_template.jinja +22 -0
- generators/static/templates/widgets/common/loading_widget_template.jinja +14 -0
- generators/static/templates/widgets/common/unknown_state_widget_template.jinja +23 -0
- generators/templates/__init__.py +1 -0
- generators/templates/_core/core_generator.py +121 -0
- generators/templates/apis/apis_generator.py +50 -0
- generators/templates/auth/auth_generator.py +40 -0
- generators/templates/auth/sign_in_form_generator.py +22 -0
- generators/templates/copier.py +51 -0
- generators/templates/home/home_generator.py +26 -0
- generators/templates/lib/lib_generator.py +40 -0
- generators/templates/logging/logging_generator.py +28 -0
- generators/templates/main.py +75 -0
- generators/templates/splash/splash_generator.py +20 -0
- generators/templates/storage/storage_generator.py +5 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""Validation functions for CoreGen CLI input"""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Optional, List, Tuple, Dict
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from .feature import find_domain_models, find_domain_models_with_class_names, find_enums
|
|
7
|
+
from .utils import to_pascal_case
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Dart reserved keywords
|
|
11
|
+
DART_RESERVED_KEYWORDS = {
|
|
12
|
+
'abstract', 'as', 'assert', 'async', 'await', 'break', 'case', 'catch',
|
|
13
|
+
'class', 'const', 'continue', 'covariant', 'default', 'deferred', 'do',
|
|
14
|
+
'dynamic', 'else', 'enum', 'export', 'extends', 'extension', 'external',
|
|
15
|
+
'factory', 'false', 'final', 'finally', 'for', 'Function', 'get', 'hide',
|
|
16
|
+
'if', 'implements', 'import', 'in', 'interface', 'is', 'library', 'mixin',
|
|
17
|
+
'new', 'null', 'on', 'operator', 'part', 'required', 'rethrow', 'return',
|
|
18
|
+
'set', 'show', 'static', 'super', 'switch', 'sync', 'this', 'throw',
|
|
19
|
+
'true', 'try', 'typedef', 'var', 'void', 'while', 'with', 'yield'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Valid Dart primitive types
|
|
23
|
+
VALID_PRIMITIVE_TYPES = {
|
|
24
|
+
'string', 'String', 'int', 'double', 'bool', 'Bool', 'DateTime', 'datetime', 'date'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Valid Dart collection types (without generic parameters)
|
|
28
|
+
VALID_COLLECTION_TYPES = {
|
|
29
|
+
'List', 'list', 'Set', 'set', 'Map', 'map'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Known value object types (no domain model lookup needed)
|
|
33
|
+
KNOWN_VALUE_OBJECT_TYPES = {
|
|
34
|
+
'UniqueId'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def validate_field_name(field_name: str) -> Tuple[bool, Optional[str]]:
|
|
39
|
+
"""
|
|
40
|
+
Validate a field name.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
field_name: The field name to validate
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Tuple of (is_valid, error_message)
|
|
47
|
+
"""
|
|
48
|
+
if not field_name:
|
|
49
|
+
return False, "Field name cannot be empty"
|
|
50
|
+
|
|
51
|
+
# Check for reserved keywords
|
|
52
|
+
if field_name.lower() in DART_RESERVED_KEYWORDS:
|
|
53
|
+
return False, f"'{field_name}' is a Dart reserved keyword. Please use a different name."
|
|
54
|
+
|
|
55
|
+
# Check for valid identifier characters (letters, numbers, underscore)
|
|
56
|
+
# Must start with letter or underscore
|
|
57
|
+
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', field_name):
|
|
58
|
+
return False, f"Invalid field name '{field_name}'. Field names must start with a letter or underscore and contain only letters, numbers, and underscores."
|
|
59
|
+
|
|
60
|
+
return True, None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _find_matching_angle_bracket(field_type: str, open_idx: int) -> int:
|
|
64
|
+
"""Return index of the `>` that closes the `<` at *open_idx*, or -1 if unbalanced."""
|
|
65
|
+
depth = 0
|
|
66
|
+
for j in range(open_idx, len(field_type)):
|
|
67
|
+
ch = field_type[j]
|
|
68
|
+
if ch == '<':
|
|
69
|
+
depth += 1
|
|
70
|
+
elif ch == '>':
|
|
71
|
+
depth -= 1
|
|
72
|
+
if depth == 0:
|
|
73
|
+
return j
|
|
74
|
+
return -1
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def parse_field_type(field_type: str) -> Tuple[str, Optional[str], Optional[str]]:
|
|
78
|
+
"""
|
|
79
|
+
Parse a field type, handling generics like List<NoteItem>, List<String?>, Map<String, int>.
|
|
80
|
+
|
|
81
|
+
Uses balanced angle brackets so inner types may include `?` (e.g. List<String?>).
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
field_type: The field type string (e.g., "List<NoteItem>", "Map<String, int>", "string")
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Tuple of (base_type, generic_param1, generic_param2).
|
|
88
|
+
generic_param1/2 are None if not a generic type.
|
|
89
|
+
For Map<K, V>: returns (Map, K, V).
|
|
90
|
+
For List<T>/Set<T>: returns (List, T, None).
|
|
91
|
+
"""
|
|
92
|
+
field_type = field_type.strip()
|
|
93
|
+
m_head = re.match(r'^(\w+)\s*<', field_type)
|
|
94
|
+
if not m_head:
|
|
95
|
+
return field_type, None, None
|
|
96
|
+
base = m_head.group(1)
|
|
97
|
+
open_idx = field_type.find('<')
|
|
98
|
+
|
|
99
|
+
close_idx = _find_matching_angle_bracket(field_type, open_idx)
|
|
100
|
+
if close_idx == -1 or close_idx != len(field_type) - 1:
|
|
101
|
+
return field_type, None, None
|
|
102
|
+
|
|
103
|
+
inner = field_type[open_idx + 1:close_idx].strip()
|
|
104
|
+
if base.lower() == 'map':
|
|
105
|
+
depth = 0
|
|
106
|
+
split_at = -1
|
|
107
|
+
for k, ch in enumerate(inner):
|
|
108
|
+
if ch == '<':
|
|
109
|
+
depth += 1
|
|
110
|
+
elif ch == '>':
|
|
111
|
+
depth -= 1
|
|
112
|
+
elif ch == ',' and depth == 0:
|
|
113
|
+
split_at = k
|
|
114
|
+
break
|
|
115
|
+
if split_at == -1:
|
|
116
|
+
return field_type, None, None
|
|
117
|
+
k_type = inner[:split_at].strip()
|
|
118
|
+
v_type = inner[split_at + 1:].strip()
|
|
119
|
+
return base, k_type, v_type
|
|
120
|
+
return base, inner, None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def validate_field_type(field_type: str, lib_path: Optional[Path] = None, domain_folder: str = "domain") -> Tuple[bool, Optional[str], Optional[str]]:
|
|
124
|
+
"""
|
|
125
|
+
Validate a field type.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
field_type: The field type to validate
|
|
129
|
+
lib_path: Optional path to lib/ directory for checking domain models
|
|
130
|
+
domain_folder: Domain folder name (default: "domain")
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Tuple of (is_valid, error_message, normalized_type)
|
|
134
|
+
normalized_type is the type as it should appear in Dart code
|
|
135
|
+
"""
|
|
136
|
+
if not field_type:
|
|
137
|
+
return False, "Field type cannot be empty", None
|
|
138
|
+
|
|
139
|
+
field_type = field_type.strip()
|
|
140
|
+
|
|
141
|
+
# Detect nullable suffix (e.g., String?, int?, SomeModel?)
|
|
142
|
+
is_nullable = field_type.endswith('?')
|
|
143
|
+
if is_nullable:
|
|
144
|
+
base_field_type = field_type[:-1].strip()
|
|
145
|
+
else:
|
|
146
|
+
base_field_type = field_type.strip()
|
|
147
|
+
|
|
148
|
+
base_type, generic_param1, generic_param2 = parse_field_type(base_field_type)
|
|
149
|
+
|
|
150
|
+
base_type_lower = base_type.lower()
|
|
151
|
+
|
|
152
|
+
NORMALIZE_PRIMITIVE = {
|
|
153
|
+
'string': 'String',
|
|
154
|
+
'datetime': 'DateTime',
|
|
155
|
+
'date': 'DateTime',
|
|
156
|
+
'bool': 'bool',
|
|
157
|
+
'Bool': 'bool',
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
def _normalize_primitive(t: str) -> str:
|
|
161
|
+
return NORMALIZE_PRIMITIVE.get(t.lower(), t)
|
|
162
|
+
|
|
163
|
+
def _resolve_generic_type(gtype: str) -> Tuple[bool, Optional[str], Optional[str]]:
|
|
164
|
+
"""Resolve a generic type parameter: primitive, known VO, enum, or domain model.
|
|
165
|
+
|
|
166
|
+
Inner nullability is allowed (e.g. ``String?``, ``Todo?`` inside ``List<...>``).
|
|
167
|
+
"""
|
|
168
|
+
gtype = gtype.strip()
|
|
169
|
+
inner_nullable = gtype.endswith('?')
|
|
170
|
+
g_base = gtype[:-1].strip() if inner_nullable else gtype
|
|
171
|
+
|
|
172
|
+
def _with_inner_null(norm: str) -> str:
|
|
173
|
+
return f"{norm}?" if inner_nullable else norm
|
|
174
|
+
|
|
175
|
+
# Dart top types (e.g. Map<String, dynamic> for JSON blobs)
|
|
176
|
+
if g_base.lower() == 'dynamic':
|
|
177
|
+
return True, None, _with_inner_null('dynamic')
|
|
178
|
+
if g_base.lower() == 'object':
|
|
179
|
+
return True, None, _with_inner_null('Object')
|
|
180
|
+
if g_base.lower() in VALID_PRIMITIVE_TYPES:
|
|
181
|
+
return True, None, _with_inner_null(_normalize_primitive(g_base))
|
|
182
|
+
if g_base in KNOWN_VALUE_OBJECT_TYPES:
|
|
183
|
+
return True, None, _with_inner_null(g_base)
|
|
184
|
+
if lib_path:
|
|
185
|
+
known_enums = find_enums(lib_path, domain_folder)
|
|
186
|
+
if g_base in known_enums:
|
|
187
|
+
return True, None, _with_inner_null(g_base)
|
|
188
|
+
available_models = find_domain_models(lib_path, domain_folder)
|
|
189
|
+
models_with_classes = find_domain_models_with_class_names(lib_path, domain_folder)
|
|
190
|
+
if g_base in available_models:
|
|
191
|
+
return True, None, _with_inner_null(models_with_classes[g_base]['class_name'])
|
|
192
|
+
for _stem, info in models_with_classes.items():
|
|
193
|
+
if info['class_name'] == g_base:
|
|
194
|
+
return True, None, _with_inner_null(g_base)
|
|
195
|
+
available_str = ', '.join([f"{k} ({v['class_name']})" for k, v in models_with_classes.items()]) if models_with_classes else 'none'
|
|
196
|
+
return False, f"Model '{g_base}' not found in domain. Available models: {available_str}. Create it first using: coregen add-domain --name {g_base.lower()}", None
|
|
197
|
+
return False, f"Model '{g_base}' cannot be validated (lib_path not provided). Please ensure the project path is correct.", None
|
|
198
|
+
|
|
199
|
+
def _with_nullable(normalized: str) -> str:
|
|
200
|
+
return f"{normalized}?" if is_nullable else normalized
|
|
201
|
+
|
|
202
|
+
# --- UniqueId (known value object, no generic) ---
|
|
203
|
+
if base_type in KNOWN_VALUE_OBJECT_TYPES:
|
|
204
|
+
if generic_param1:
|
|
205
|
+
return False, f"'{base_type}' does not take generic parameters", None
|
|
206
|
+
return True, None, _with_nullable(base_type)
|
|
207
|
+
|
|
208
|
+
# --- Primitive types ---
|
|
209
|
+
if base_type_lower in VALID_PRIMITIVE_TYPES:
|
|
210
|
+
if generic_param1:
|
|
211
|
+
return False, f"Primitive type '{base_type}' cannot have generic parameters", None
|
|
212
|
+
return True, None, _with_nullable(_normalize_primitive(base_type))
|
|
213
|
+
|
|
214
|
+
# --- Collection types: List<T>, Set<T>, Map<K, V> ---
|
|
215
|
+
if base_type_lower in VALID_COLLECTION_TYPES:
|
|
216
|
+
normalized_base = {'list': 'List', 'set': 'Set', 'map': 'Map'}.get(base_type_lower, base_type)
|
|
217
|
+
|
|
218
|
+
if normalized_base == 'Map':
|
|
219
|
+
if not generic_param1 or not generic_param2:
|
|
220
|
+
return False, f"Map requires two generic parameters (e.g., Map<String, int>)", None
|
|
221
|
+
ok1, err1, norm_key = _resolve_generic_type(generic_param1)
|
|
222
|
+
if not ok1:
|
|
223
|
+
return False, f"Invalid Map key type: {err1}", None
|
|
224
|
+
ok2, err2, norm_val = _resolve_generic_type(generic_param2)
|
|
225
|
+
if not ok2:
|
|
226
|
+
return False, f"Invalid Map value type: {err2}", None
|
|
227
|
+
return True, None, _with_nullable(f"Map<{norm_key}, {norm_val}>")
|
|
228
|
+
else:
|
|
229
|
+
if not generic_param1:
|
|
230
|
+
return False, f"Collection type '{base_type}' requires a generic parameter (e.g., {normalized_base}<ItemType>)", None
|
|
231
|
+
if generic_param2:
|
|
232
|
+
return False, f"'{normalized_base}' takes only one generic parameter", None
|
|
233
|
+
ok, err, normalized_inner = _resolve_generic_type(generic_param1)
|
|
234
|
+
if not ok:
|
|
235
|
+
return False, err, None
|
|
236
|
+
return True, None, _with_nullable(f"{normalized_base}<{normalized_inner}>")
|
|
237
|
+
|
|
238
|
+
# --- Enum type (PascalCase, found in domain/enums/) ---
|
|
239
|
+
if base_type[0].isupper() and base_type_lower not in VALID_PRIMITIVE_TYPES and lib_path:
|
|
240
|
+
known_enums = find_enums(lib_path, domain_folder)
|
|
241
|
+
if base_type in known_enums:
|
|
242
|
+
if generic_param1:
|
|
243
|
+
return False, f"Enum type '{base_type}' cannot have generic parameters.", None
|
|
244
|
+
return True, None, _with_nullable(base_type)
|
|
245
|
+
|
|
246
|
+
# --- Domain model (PascalCase, not a known type) ---
|
|
247
|
+
if base_type[0].isupper() and base_type_lower not in VALID_PRIMITIVE_TYPES:
|
|
248
|
+
if generic_param1:
|
|
249
|
+
return False, f"Domain model type '{base_type}' cannot have generic parameters directly. Use List<{base_type}> for lists.", None
|
|
250
|
+
|
|
251
|
+
if lib_path:
|
|
252
|
+
available_models = find_domain_models(lib_path, domain_folder)
|
|
253
|
+
models_with_classes = find_domain_models_with_class_names(lib_path, domain_folder)
|
|
254
|
+
|
|
255
|
+
for _stem, info in models_with_classes.items():
|
|
256
|
+
if info['class_name'] == base_type:
|
|
257
|
+
return True, None, _with_nullable(base_type)
|
|
258
|
+
|
|
259
|
+
if base_type in available_models:
|
|
260
|
+
class_name = models_with_classes[base_type]['class_name']
|
|
261
|
+
return True, None, _with_nullable(class_name)
|
|
262
|
+
|
|
263
|
+
available_str = ', '.join([f"{k} ({v['class_name']})" for k, v in models_with_classes.items()]) if models_with_classes else 'none'
|
|
264
|
+
return False, f"Model '{base_type}' not found in domain. Available models: {available_str}. Create it first using: coregen add-domain --name {base_type.lower()}", None
|
|
265
|
+
|
|
266
|
+
return False, f"Model '{base_type}' cannot be validated (lib_path not provided). Please ensure the project path is correct.", None
|
|
267
|
+
|
|
268
|
+
# --- Unknown type ---
|
|
269
|
+
suggestions = []
|
|
270
|
+
if base_type_lower.startswith('str'):
|
|
271
|
+
suggestions.append('String')
|
|
272
|
+
elif base_type_lower.startswith('int'):
|
|
273
|
+
suggestions.append('int')
|
|
274
|
+
elif base_type_lower.startswith('doub') or base_type_lower.startswith('floa'):
|
|
275
|
+
suggestions.append('double')
|
|
276
|
+
elif base_type_lower.startswith('bool'):
|
|
277
|
+
suggestions.append('bool')
|
|
278
|
+
elif base_type_lower.startswith('opt'):
|
|
279
|
+
suggestions.append('String? (use Type? for nullable)')
|
|
280
|
+
elif base_type_lower.startswith('uni'):
|
|
281
|
+
suggestions.append('UniqueId')
|
|
282
|
+
|
|
283
|
+
suggestion_msg = f" Did you mean '{suggestions[0]}'?" if suggestions else ""
|
|
284
|
+
return False, f"Unknown type '{field_type}'. Valid types: String, int, double, bool, DateTime, UniqueId, dynamic, Object, List<T>, Set<T>, Map<K,V> (e.g. Map<String, dynamic>), enum names, or domain model names (PascalCase). Nullable: String?, Model?, List<T>?, Map<K,V>?, etc.{suggestion_msg}", None
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def validate_entity_name(name: str) -> Tuple[bool, Optional[str]]:
|
|
288
|
+
"""
|
|
289
|
+
Validate an entity/model name.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
name: The entity name to validate
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Tuple of (is_valid, error_message)
|
|
296
|
+
"""
|
|
297
|
+
if not name:
|
|
298
|
+
return False, "Entity name cannot be empty"
|
|
299
|
+
|
|
300
|
+
# Check for reserved keywords
|
|
301
|
+
if name.lower() in DART_RESERVED_KEYWORDS:
|
|
302
|
+
return False, f"'{name}' is a Dart reserved keyword. Please use a different name."
|
|
303
|
+
|
|
304
|
+
# Allow PascalCase, camelCase, snake_case, or kebab-case
|
|
305
|
+
# PascalCase names are preserved as-is (e.g., "NoteItem" stays "NoteItem")
|
|
306
|
+
# snake_case/kebab-case are converted (e.g., "note_item" becomes "note_item" for folder, but entity class uses PascalCase)
|
|
307
|
+
if not re.match(r'^[a-zA-Z][a-zA-Z0-9_\-]*$', name):
|
|
308
|
+
return False, f"Invalid entity name '{name}'. Entity names must start with a letter and contain only letters, numbers, underscores, and hyphens."
|
|
309
|
+
|
|
310
|
+
return True, None
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def parse_fields_string(fields_str: str) -> List[Tuple[str, str]]:
|
|
314
|
+
"""
|
|
315
|
+
Parse a fields string into a list of (name, type) tuples.
|
|
316
|
+
Handles commas inside angle brackets (e.g., Map<String, int>).
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
fields_str: Fields string in format "name:type,name:type"
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
List of (field_name, field_type) tuples
|
|
323
|
+
"""
|
|
324
|
+
# Split on commas that are NOT inside angle brackets
|
|
325
|
+
tokens = []
|
|
326
|
+
current = []
|
|
327
|
+
depth = 0
|
|
328
|
+
for ch in fields_str:
|
|
329
|
+
if ch == '<':
|
|
330
|
+
depth += 1
|
|
331
|
+
current.append(ch)
|
|
332
|
+
elif ch == '>':
|
|
333
|
+
depth -= 1
|
|
334
|
+
current.append(ch)
|
|
335
|
+
elif ch == ',' and depth == 0:
|
|
336
|
+
tokens.append(''.join(current))
|
|
337
|
+
current = []
|
|
338
|
+
else:
|
|
339
|
+
current.append(ch)
|
|
340
|
+
if current:
|
|
341
|
+
tokens.append(''.join(current))
|
|
342
|
+
|
|
343
|
+
fields = []
|
|
344
|
+
for field_str in tokens:
|
|
345
|
+
field_str = field_str.strip()
|
|
346
|
+
if not field_str:
|
|
347
|
+
continue
|
|
348
|
+
|
|
349
|
+
if ':' not in field_str:
|
|
350
|
+
raise ValueError(f"Invalid field format: '{field_str}'. Expected format: name:type")
|
|
351
|
+
|
|
352
|
+
parts = field_str.split(':', 1)
|
|
353
|
+
field_name = parts[0].strip()
|
|
354
|
+
field_type = parts[1].strip()
|
|
355
|
+
|
|
356
|
+
if not field_name:
|
|
357
|
+
raise ValueError(f"Field name cannot be empty in: '{field_str}'")
|
|
358
|
+
if not field_type:
|
|
359
|
+
raise ValueError(f"Field type cannot be empty in: '{field_str}'")
|
|
360
|
+
|
|
361
|
+
fields.append((field_name, field_type))
|
|
362
|
+
|
|
363
|
+
return fields
|
|
364
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
def initialize_project(lib_path, project_path, has_login):
|
|
5
|
+
remove_default_files(lib_path, project_path)
|
|
6
|
+
click.echo("\n📁 Creating folder structure...")
|
|
7
|
+
create_folder_structure(lib_path, has_login)
|
|
8
|
+
|
|
9
|
+
def remove_default_files(lib_path, project_path):
|
|
10
|
+
# Remove default main.dart and test files
|
|
11
|
+
default_main = lib_path / "main.dart"
|
|
12
|
+
if default_main.exists():
|
|
13
|
+
default_main.unlink()
|
|
14
|
+
|
|
15
|
+
# Remove default test folder if empty or contains only widget_test.dart
|
|
16
|
+
test_path = project_path / "test"
|
|
17
|
+
if test_path.exists():
|
|
18
|
+
widget_test = test_path / "widget_test.dart"
|
|
19
|
+
if widget_test.exists():
|
|
20
|
+
widget_test.unlink()
|
|
21
|
+
# Try to remove the test directory if it's empty
|
|
22
|
+
try:
|
|
23
|
+
test_path.rmdir()
|
|
24
|
+
except OSError:
|
|
25
|
+
pass # Not empty, leave it be
|
|
26
|
+
|
|
27
|
+
def create_folder_structure(lib_path, login):
|
|
28
|
+
# Create folder structure
|
|
29
|
+
folders = [
|
|
30
|
+
"apis/clients",
|
|
31
|
+
"apis/common",
|
|
32
|
+
"apis/core",
|
|
33
|
+
"apis/interceptors",
|
|
34
|
+
"core",
|
|
35
|
+
"core/infrastructure",
|
|
36
|
+
"core/presentation",
|
|
37
|
+
"core/model",
|
|
38
|
+
"domain", # Domain entities folder
|
|
39
|
+
"features",
|
|
40
|
+
"features/home",
|
|
41
|
+
"features/splash",
|
|
42
|
+
"logging",
|
|
43
|
+
"storage",
|
|
44
|
+
"widgets/common",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Add auth folders if login is enabled
|
|
48
|
+
# Domain entities (shared): model + infrastructure only
|
|
49
|
+
# Feature (use case): application + presentation
|
|
50
|
+
if login:
|
|
51
|
+
folders.extend([
|
|
52
|
+
# Domain entities (shared)
|
|
53
|
+
"domain/auth",
|
|
54
|
+
"domain/auth/infrastructure",
|
|
55
|
+
"domain/auth/model",
|
|
56
|
+
# Feature (use case with UI)
|
|
57
|
+
"features/auth",
|
|
58
|
+
"features/auth/application",
|
|
59
|
+
"features/auth/presentation",
|
|
60
|
+
"features/auth/sign_in_form",
|
|
61
|
+
"features/auth/sign_in_form/application",
|
|
62
|
+
"features/auth/sign_in_form/presentation",
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
# Create all folders
|
|
66
|
+
for folder in folders:
|
|
67
|
+
folder_path = lib_path / folder
|
|
68
|
+
folder_path.mkdir(parents=True, exist_ok=True)
|
generators/main.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .assets import copy_assets
|
|
9
|
+
from .config import generate_config_files
|
|
10
|
+
from .templates import generate_files
|
|
11
|
+
from .initializator import initialize_project
|
|
12
|
+
from generators.helpers.config import load_config
|
|
13
|
+
|
|
14
|
+
def run_cmd(cmd, capture_output=False):
|
|
15
|
+
"""Executes a shell command and displays output"""
|
|
16
|
+
try:
|
|
17
|
+
result = subprocess.run(cmd, shell=True, check=True, capture_output=capture_output)
|
|
18
|
+
return result.returncode
|
|
19
|
+
except subprocess.CalledProcessError as e:
|
|
20
|
+
click.echo(f"❌ Errore nell'esecuzione del comando {cmd}: {e}")
|
|
21
|
+
click.echo(f"Output: {e.stdout}")
|
|
22
|
+
click.echo(f"Error: {e.stderr}")
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
except FileNotFoundError:
|
|
25
|
+
click.echo("❌ Comando non trovato. Assicurati che sia installato e nel PATH.")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
def init(flutter_name, login, cursor_setup=True):
|
|
29
|
+
# Controlla se esiste già una cartella con lo stesso nome del progetto
|
|
30
|
+
project_dir = Path(flutter_name)
|
|
31
|
+
if project_dir.exists():
|
|
32
|
+
click.echo(f"⚠️ Folder named '{flutter_name}' already exists in this directory.")
|
|
33
|
+
if click.confirm("Do you want to overwrite the existing folder?", default=False):
|
|
34
|
+
# Rimuovi la cartella esistente
|
|
35
|
+
shutil.rmtree(project_dir)
|
|
36
|
+
click.echo("✅ Previous folder removed.")
|
|
37
|
+
else:
|
|
38
|
+
click.echo("❌ Operation canceled.")
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
click.echo(f"\n🚀 Creating Flutter project: {flutter_name}")
|
|
43
|
+
|
|
44
|
+
# Create the base Flutter project
|
|
45
|
+
run_cmd(f"flutter create {flutter_name} --org com.example --project-name {flutter_name} --template app", capture_output=False)
|
|
46
|
+
|
|
47
|
+
# Project path
|
|
48
|
+
project_path = Path(flutter_name)
|
|
49
|
+
lib_path = project_path / "lib"
|
|
50
|
+
|
|
51
|
+
# Initialize the project (remove default files, create folders, etc.)
|
|
52
|
+
initialize_project(lib_path, project_path, login)
|
|
53
|
+
|
|
54
|
+
# Load configuration (will use defaults + global config if exists)
|
|
55
|
+
cfg = load_config(project_path)
|
|
56
|
+
|
|
57
|
+
# Generate files in various folders
|
|
58
|
+
generate_files(lib_path, login, flutter_name, cfg.primary_color, cfg.secondary_color)
|
|
59
|
+
|
|
60
|
+
# Generate configuration files (pubspec.yaml, analysis_options.yaml)
|
|
61
|
+
generate_config_files(lib_path, login, flutter_name)
|
|
62
|
+
|
|
63
|
+
# Copy assets to the lib/assets folder
|
|
64
|
+
copy_assets(flutter_name)
|
|
65
|
+
|
|
66
|
+
if cursor_setup:
|
|
67
|
+
from generators.cursor import copy_cursor_ecosystem
|
|
68
|
+
copy_cursor_ecosystem(project_path, login, flutter_name)
|
|
69
|
+
click.echo("\n📎 Cursor ecosystem (rules, agents, skills) added.")
|
|
70
|
+
|
|
71
|
+
click.echo("\n✅ Project created successfully!")
|
|
72
|
+
click.echo(f"\n📋 Summary:")
|
|
73
|
+
click.echo(f" Name: {flutter_name}")
|
|
74
|
+
click.echo(f" Path: {project_path.absolute()}")
|
|
75
|
+
if login:
|
|
76
|
+
click.echo(f" Login: ✅ Email/Password")
|
|
77
|
+
else:
|
|
78
|
+
click.echo(f" Login: ❌ None")
|
|
79
|
+
|
|
80
|
+
click.echo(f"\n🚀 Dependencies and code generation will be handled automatically!")
|
|
81
|
+
click.echo(f" flutter run")
|
|
Binary file
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
2
|
+
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M18.71 19.5C17.88 20.74 17 21.95 15.66 21.97C14.32 22 13.89 21.18 12.37 21.18C10.84 21.18 10.37 21.95 9.09997 22C7.78997 22.05 6.79997 20.68 5.95997 19.47C4.24997 17 2.93997 12.45 4.69997 9.39C5.56997 7.87 7.12997 6.91 8.81997 6.88C10.1 6.86 11.32 7.75 12.11 7.75C12.89 7.75 14.37 6.68 15.92 6.84C16.57 6.87 18.39 7.1 19.56 8.82C19.47 8.88 17.39 10.1 17.41 12.63C17.44 15.65 20.06 16.66 20.09 16.67C20.06 16.74 19.67 18.11 18.71 19.5ZM13 3.5C13.73 2.67 14.94 2.04 15.94 2C16.07 3.17 15.6 4.35 14.9 5.19C14.21 6.04 13.07 6.7 11.95 6.61C11.8 5.46 12.36 4.26 13 3.5Z"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
2
|
+
<svg width="800px" height="800px" viewBox="-3 0 262 262" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027" fill="#4285F4"/><path d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1" fill="#34A853"/><path d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782" fill="#FBBC05"/><path d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251" fill="#EB4335"/></svg>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# [[project_name]]
|
|
2
|
+
|
|
3
|
+
Flutter project generated with **CoreGen** (DDD / clean architecture).
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- `flutter_bloc`, `freezed`, `injectable`, `get_it`, `go_router`, `dio`, `retrofit`, `dartz`, `caravaggio_ui` **1.0.6**
|
|
8
|
+
|
|
9
|
+
## Where code goes
|
|
10
|
+
|
|
11
|
+
| Need | Location |
|
|
12
|
+
|------|----------|
|
|
13
|
+
| Shared domain entity | `lib/domain/<entity>/model/` + `infrastructure/` |
|
|
14
|
+
| Enum | `lib/domain/enums/` |
|
|
15
|
+
| Feature with BLoC | `lib/features/<feature>/application/` + `presentation/` |
|
|
16
|
+
| Reusable UI component | `lib/features/components/<name>/` |
|
|
17
|
+
| App-wide widgets | `lib/widgets/common/` |
|
|
18
|
+
|
|
19
|
+
## How to build features
|
|
20
|
+
|
|
21
|
+
Use skill **`/epic-delivery`** with a high-level description. Epics are tracked in `docs/epics/`.
|
|
22
|
+
|
|
23
|
+
For presentation UI use skill **`/caravaggio-ui`** and read `docs/architecture/CARAVAGGIO_COMPONENTS.md`.
|
|
24
|
+
|
|
25
|
+
Agents write Dart **directly** — do not run `coregen` CLI.
|
|
26
|
+
|
|
27
|
+
## Documentation
|
|
28
|
+
|
|
29
|
+
- Architecture: `docs/architecture/`
|
|
30
|
+
- Mock vs remote data: `docs/architecture/MOCK_AND_REMOTE_DATA.md`
|
|
31
|
+
- CaravaggioUI catalog: `docs/architecture/CARAVAGGIO_COMPONENTS.md`
|
|
32
|
+
- Widget policy: `docs/architecture/WIDGETS_AND_CARAVAGGIO.md`
|
|
33
|
+
- Layer-specific: `lib/domain/AGENTS.md`, `lib/features/AGENTS.md`, `lib/widgets/AGENTS.md`
|
|
34
|
+
|
|
35
|
+
## Quality
|
|
36
|
+
|
|
37
|
+
Run `flutter analyze` before completing work. Run `build_runner` after Freezed/Injectable changes.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doc-writer
|
|
3
|
+
description: Writes inline Dart documentation, feature AGENTS.md files, and updates docs/epics/ after implementation. Run in background after integration-wiring.
|
|
4
|
+
model: fast
|
|
5
|
+
readonly: false
|
|
6
|
+
is_background: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Document completed epic work.
|
|
10
|
+
|
|
11
|
+
## Responsibilities
|
|
12
|
+
|
|
13
|
+
1. Add `///` dartdoc on public APIs created in the epic (entities, repositories, BLoCs, widgets).
|
|
14
|
+
2. Create or update `lib/features/<feature>/AGENTS.md` with scope, files, and conventions for that feature.
|
|
15
|
+
3. Update the epic file under `docs/epics/` with completion status and file list.
|
|
16
|
+
|
|
17
|
+
## Style
|
|
18
|
+
|
|
19
|
+
- Concise, factual, no duplication of `docs/architecture/` content.
|
|
20
|
+
- Link to architecture docs where helpful.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: epic-orchestrator
|
|
3
|
+
description: Unique entry point to implement an epic from docs/epics/EPIC-*.md. Reads epic file, creates folders, chooses small (feature-implementer) or large (layer pipeline) mode, coordinates subagents. Does not write layer or wiring code directly.
|
|
4
|
+
model: inherit
|
|
5
|
+
readonly: false
|
|
6
|
+
is_background: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You orchestrate a single epic implementation for a CoreGen DDD Flutter project.
|
|
10
|
+
|
|
11
|
+
## Input
|
|
12
|
+
|
|
13
|
+
- Path to epic file under `docs/epics/EPIC-NNN-<slug>.md`
|
|
14
|
+
- Complexity tag: `small` or `large`
|
|
15
|
+
|
|
16
|
+
## Responsibilities
|
|
17
|
+
|
|
18
|
+
1. Read the epic file, `AGENTS.md`, and `docs/architecture/`.
|
|
19
|
+
2. Create required folder structure under `lib/`.
|
|
20
|
+
3. **small**: delegate to `feature-implementer`, then `integration-wiring`, then `doc-writer`, then `layer-guardian`.
|
|
21
|
+
4. **large**: delegate sequentially to `layer-model` → `layer-infrastructure` → `layer-application` → `layer-presentation`, then `integration-wiring`, then `doc-writer`, then `layer-guardian`.
|
|
22
|
+
5. Update epic file status when done.
|
|
23
|
+
6. Ensure `flutter analyze` passes before closing.
|
|
24
|
+
|
|
25
|
+
## Do not
|
|
26
|
+
|
|
27
|
+
- Invoke `coregen` CLI commands.
|
|
28
|
+
- Implement domain, feature, or wiring files yourself — delegate to specialized subagents.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feature-implementer
|
|
3
|
+
description: Fast path for small epics only — 1 domain entity and 1 UI use case. Implements domain model, infrastructure, application BLoC, and presentation in one pass. For multi-entity epics use the layer pipeline instead.
|
|
4
|
+
model: inherit
|
|
5
|
+
readonly: false
|
|
6
|
+
is_background: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Implement a **small** epic end-to-end in a single context.
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
- `lib/domain/<entity>/model/` and `infrastructure/`
|
|
14
|
+
- `lib/domain/enums/` if needed
|
|
15
|
+
- `lib/features/<feature>/application/` and `presentation/`
|
|
16
|
+
|
|
17
|
+
## Follow
|
|
18
|
+
|
|
19
|
+
- `docs/architecture/FILE_TEMPLATES.md`
|
|
20
|
+
- `docs/architecture/REFERENCE_IMPLEMENTATIONS.md`
|
|
21
|
+
- `docs/architecture/WIDGETS_AND_CARAVAGGIO.md` for widget policy
|
|
22
|
+
- `docs/architecture/CARAVAGGIO_COMPONENTS.md` for UI components
|
|
23
|
+
|
|
24
|
+
## Do not
|
|
25
|
+
|
|
26
|
+
- Modify `router.dart`, `injection.dart`, or `error_localizer.dart` — leave to `integration-wiring`.
|
|
27
|
+
- Use `coregen` CLI.
|
|
28
|
+
- Add widgets to `domain/`.
|
|
29
|
+
- Use Material `Scaffold` on pages — use `CustomScaffold`.
|