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.
Files changed (172) hide show
  1. coregen.py +1585 -0
  2. coregen_cli-3.1.6.dist-info/METADATA +1211 -0
  3. coregen_cli-3.1.6.dist-info/RECORD +172 -0
  4. coregen_cli-3.1.6.dist-info/WHEEL +5 -0
  5. coregen_cli-3.1.6.dist-info/entry_points.txt +2 -0
  6. coregen_cli-3.1.6.dist-info/top_level.txt +2 -0
  7. generators/__init__.py +1 -0
  8. generators/assets/__init__.py +1 -0
  9. generators/assets/assets.py +30 -0
  10. generators/assets/main.py +4 -0
  11. generators/config/__init__.py +1 -0
  12. generators/config/analisy_options.py +42 -0
  13. generators/config/main.py +11 -0
  14. generators/config/pubspec.py +139 -0
  15. generators/cursor/__init__.py +3 -0
  16. generators/cursor/setup.py +87 -0
  17. generators/helpers/__init__.py +56 -0
  18. generators/helpers/component.py +1030 -0
  19. generators/helpers/config.py +255 -0
  20. generators/helpers/data_source.py +180 -0
  21. generators/helpers/domain.py +469 -0
  22. generators/helpers/feature.py +654 -0
  23. generators/helpers/navigation.py +274 -0
  24. generators/helpers/page.py +63 -0
  25. generators/helpers/project.py +33 -0
  26. generators/helpers/utils.py +338 -0
  27. generators/helpers/validation.py +364 -0
  28. generators/initializator.py +68 -0
  29. generators/main.py +81 -0
  30. generators/static/assets/logo.png +0 -0
  31. generators/static/assets/mock/auth_user.json +5 -0
  32. generators/static/assets/svgs/apple.svg +4 -0
  33. generators/static/assets/svgs/google.svg +2 -0
  34. generators/static/cursor/AGENTS.md.jinja +37 -0
  35. generators/static/cursor/agents/doc-writer.md +20 -0
  36. generators/static/cursor/agents/epic-orchestrator.md +28 -0
  37. generators/static/cursor/agents/feature-implementer.md +29 -0
  38. generators/static/cursor/agents/integration-wiring.md +30 -0
  39. generators/static/cursor/agents/layer-application.md +21 -0
  40. generators/static/cursor/agents/layer-guardian.md +21 -0
  41. generators/static/cursor/agents/layer-infrastructure.md +31 -0
  42. generators/static/cursor/agents/layer-model.md +24 -0
  43. generators/static/cursor/agents/layer-presentation.md +33 -0
  44. generators/static/cursor/docs/architecture/APIS_AND_INTEGRATION.md.jinja +42 -0
  45. generators/static/cursor/docs/architecture/CARAVAGGIO_COMPONENTS.md.jinja +317 -0
  46. generators/static/cursor/docs/architecture/DDD_LAYERS.md.jinja +32 -0
  47. generators/static/cursor/docs/architecture/FILE_TEMPLATES.md.jinja +76 -0
  48. generators/static/cursor/docs/architecture/MOCK_AND_REMOTE_DATA.md.jinja +87 -0
  49. generators/static/cursor/docs/architecture/REFERENCE_IMPLEMENTATIONS.md.jinja +48 -0
  50. generators/static/cursor/docs/architecture/WIDGETS_AND_CARAVAGGIO.md.jinja +76 -0
  51. generators/static/cursor/docs/epics/README.md +17 -0
  52. generators/static/cursor/lib/domain/AGENTS.md +23 -0
  53. generators/static/cursor/lib/features/AGENTS.md +23 -0
  54. generators/static/cursor/lib/widgets/AGENTS.md +21 -0
  55. generators/static/cursor/rules/apis-layer.mdc.jinja +21 -0
  56. generators/static/cursor/rules/application-layer.mdc.jinja +12 -0
  57. generators/static/cursor/rules/architecture-core.mdc.jinja +43 -0
  58. generators/static/cursor/rules/dart-conventions.mdc.jinja +14 -0
  59. generators/static/cursor/rules/domain-layer.mdc.jinja +24 -0
  60. generators/static/cursor/rules/login-context.mdc.jinja +16 -0
  61. generators/static/cursor/rules/presentation-layer.mdc.jinja +15 -0
  62. generators/static/cursor/rules/quality-gate.mdc.jinja +14 -0
  63. generators/static/cursor/rules/ui-caravaggio.mdc.jinja +22 -0
  64. generators/static/cursor/skills/caravaggio-ui/SKILL.md +138 -0
  65. generators/static/cursor/skills/epic-delivery/SKILL.md +74 -0
  66. generators/static/cursor/skills/epic-delivery/templates/epic-status.md +14 -0
  67. generators/static/cursor/skills/epic-delivery/templates/epic.md +36 -0
  68. generators/static/templates/apis/common/constants_template.jinja +48 -0
  69. generators/static/templates/apis/common/data_source_config_template.jinja +47 -0
  70. generators/static/templates/apis/core/api_injectable_module_template.jinja +14 -0
  71. generators/static/templates/apis/interceptors/api_logger_template.jinja +135 -0
  72. generators/static/templates/apis/interceptors/auth_interceptor_template.jinja +207 -0
  73. generators/static/templates/auth/application/auth_bloc_template.jinja +36 -0
  74. generators/static/templates/auth/application/auth_event_template.jinja +7 -0
  75. generators/static/templates/auth/application/auth_state_template.jinja +8 -0
  76. generators/static/templates/auth/infrastructure/auth_facade_module_template.jinja +16 -0
  77. generators/static/templates/auth/infrastructure/auth_facade_template.jinja +288 -0
  78. generators/static/templates/auth/infrastructure/mock_auth_facade_template.jinja +106 -0
  79. generators/static/templates/auth/model/auth_failure_template.jinja +11 -0
  80. generators/static/templates/auth/model/i_auth_facade_template.jinja +24 -0
  81. generators/static/templates/auth/model/user_template.jinja +34 -0
  82. generators/static/templates/auth/model/value_objects_template.jinja +54 -0
  83. generators/static/templates/auth/presentation/login_screen_template.jinja +24 -0
  84. generators/static/templates/auth/sign_in_form/application/sign_in_form_bloc_template.jinja +139 -0
  85. generators/static/templates/auth/sign_in_form/application/sign_in_form_event_template.jinja +15 -0
  86. generators/static/templates/auth/sign_in_form/application/sign_in_form_state_template.jinja +26 -0
  87. generators/static/templates/auth/sign_in_form/presentation/apple_form_template.jinja +16 -0
  88. generators/static/templates/auth/sign_in_form/presentation/email_password_form_template_INACTIVE.jinja +94 -0
  89. generators/static/templates/auth/sign_in_form/presentation/google_form_template.jinja +15 -0
  90. generators/static/templates/auth/sign_in_form/presentation/sign_in_form_template.jinja +381 -0
  91. generators/static/templates/component/component_bloc_template.jinja +49 -0
  92. generators/static/templates/component/component_event_template.jinja +7 -0
  93. generators/static/templates/component/component_form_bloc_template.jinja +56 -0
  94. generators/static/templates/component/component_form_event_template.jinja +7 -0
  95. generators/static/templates/component/component_form_state_template.jinja +18 -0
  96. generators/static/templates/component/component_form_widget_template.jinja +130 -0
  97. generators/static/templates/component/component_list_widget_template.jinja +95 -0
  98. generators/static/templates/component/component_state_template.jinja +9 -0
  99. generators/static/templates/component/component_widget_template.jinja +113 -0
  100. generators/static/templates/core/bloc/base_form_bloc_template.jinja +32 -0
  101. generators/static/templates/core/errors/error_localizer_template.jinja +61 -0
  102. generators/static/templates/core/infrastructure/base_mapper_template.jinja +21 -0
  103. generators/static/templates/core/infrastructure/base_repository_mixin_template.jinja +78 -0
  104. generators/static/templates/core/infrastructure/firebase_injectable_module_template.jinja +11 -0
  105. generators/static/templates/core/infrastructure/firestore_helpers_template.jinja +22 -0
  106. generators/static/templates/core/infrastructure/repository_error_handler_template.jinja +44 -0
  107. generators/static/templates/core/model/common_interfaces_template.jinja +3 -0
  108. generators/static/templates/core/model/entity_template.jinja +5 -0
  109. generators/static/templates/core/model/errors_template.jinja +15 -0
  110. generators/static/templates/core/model/failures_template.jinja +97 -0
  111. generators/static/templates/core/model/value_objects_template.jinja +135 -0
  112. generators/static/templates/core/model/value_validators_template.jinja +105 -0
  113. generators/static/templates/core/presentation/app_drawer_template.jinja +52 -0
  114. generators/static/templates/core/presentation/app_widget_template.jinja +41 -0
  115. generators/static/templates/core/presentation/bottom_nav_bar_template.jinja +32 -0
  116. generators/static/templates/domain/domain_mapper_template.jinja +39 -0
  117. generators/static/templates/domain/domain_remote_service_template.jinja +46 -0
  118. generators/static/templates/domain/domain_repository_template.jinja +90 -0
  119. generators/static/templates/domain/domain_service_module_template.jinja +23 -0
  120. generators/static/templates/domain/enum_template.jinja +3 -0
  121. generators/static/templates/domain/i_domain_service_template.jinja +18 -0
  122. generators/static/templates/domain/mock_domain_service_template.jinja +73 -0
  123. generators/static/templates/feature/feature_bloc_template.jinja +104 -0
  124. generators/static/templates/feature/feature_dto_template.jinja +26 -0
  125. generators/static/templates/feature/feature_entity_template.jinja +23 -0
  126. generators/static/templates/feature/feature_event_template.jinja +20 -0
  127. generators/static/templates/feature/feature_extensions_template.jinja +25 -0
  128. generators/static/templates/feature/feature_failure_template.jinja +11 -0
  129. generators/static/templates/feature/feature_page_template.jinja +113 -0
  130. generators/static/templates/feature/feature_repository_template.jinja +112 -0
  131. generators/static/templates/feature/feature_state_template.jinja +20 -0
  132. generators/static/templates/feature/i_feature_repository_template.jinja +21 -0
  133. generators/static/templates/feature/value_object_field_template.jinja +0 -0
  134. generators/static/templates/feature/value_object_template.jinja +0 -0
  135. generators/static/templates/feature/value_validators_template.jinja +57 -0
  136. generators/static/templates/home/home_screen_template.jinja +68 -0
  137. generators/static/templates/home/presentation/home_screen_template.jinja +52 -0
  138. generators/static/templates/home/screen_template.jinja +12 -0
  139. generators/static/templates/injection_template.jinja +8 -0
  140. generators/static/templates/logging/analytics_logging_template.jinja +42 -0
  141. generators/static/templates/logging/console_template.jinja +50 -0
  142. generators/static/templates/logging/logger_injectable_module_template.jinja +24 -0
  143. generators/static/templates/logging/logger_template.jinja +118 -0
  144. generators/static/templates/main_template.jinja +43 -0
  145. generators/static/templates/page_template.jinja +29 -0
  146. generators/static/templates/router_template.jinja +26 -0
  147. generators/static/templates/splash/presentation/splash_screen_auth_template.jinja +97 -0
  148. generators/static/templates/splash/presentation/splash_screen_template.jinja +51 -0
  149. generators/static/templates/storage/storage_repository_template.jinja +80 -0
  150. generators/static/templates/user_profile/infrastructure/user_profile_dto_template.jinja +27 -0
  151. generators/static/templates/user_profile/infrastructure/user_profile_mapper_template.jinja +43 -0
  152. generators/static/templates/user_profile/infrastructure/user_profile_repository_template.jinja +82 -0
  153. generators/static/templates/user_profile/infrastructure/user_profile_service_template.jinja +27 -0
  154. generators/static/templates/user_profile/model/i_user_profile_repository_template.jinja +34 -0
  155. generators/static/templates/user_profile/model/user_profile_failure_template.jinja +11 -0
  156. generators/static/templates/user_profile/model/user_profile_template.jinja +31 -0
  157. generators/static/templates/widgets/common/custom_scaffold_template.jinja +52 -0
  158. generators/static/templates/widgets/common/error_widget_template.jinja +22 -0
  159. generators/static/templates/widgets/common/loading_widget_template.jinja +14 -0
  160. generators/static/templates/widgets/common/unknown_state_widget_template.jinja +23 -0
  161. generators/templates/__init__.py +1 -0
  162. generators/templates/_core/core_generator.py +121 -0
  163. generators/templates/apis/apis_generator.py +50 -0
  164. generators/templates/auth/auth_generator.py +40 -0
  165. generators/templates/auth/sign_in_form_generator.py +22 -0
  166. generators/templates/copier.py +51 -0
  167. generators/templates/home/home_generator.py +26 -0
  168. generators/templates/lib/lib_generator.py +40 -0
  169. generators/templates/logging/logging_generator.py +28 -0
  170. generators/templates/main.py +75 -0
  171. generators/templates/splash/splash_generator.py +20 -0
  172. 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,5 @@
1
+ {
2
+ "id": "mock-user-1",
3
+ "name": "Demo User",
4
+ "emailAddress": "demo@example.com"
5
+ }
@@ -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`.