atdd 0.2.1__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 (184) hide show
  1. atdd/__init__.py +6 -0
  2. atdd/__main__.py +4 -0
  3. atdd/cli.py +404 -0
  4. atdd/coach/__init__.py +0 -0
  5. atdd/coach/commands/__init__.py +0 -0
  6. atdd/coach/commands/add_persistence_metadata.py +215 -0
  7. atdd/coach/commands/analyze_migrations.py +188 -0
  8. atdd/coach/commands/consumers.py +720 -0
  9. atdd/coach/commands/infer_governance_status.py +149 -0
  10. atdd/coach/commands/initializer.py +177 -0
  11. atdd/coach/commands/interface.py +1078 -0
  12. atdd/coach/commands/inventory.py +565 -0
  13. atdd/coach/commands/migration.py +240 -0
  14. atdd/coach/commands/registry.py +1560 -0
  15. atdd/coach/commands/session.py +430 -0
  16. atdd/coach/commands/sync.py +405 -0
  17. atdd/coach/commands/test_interface.py +399 -0
  18. atdd/coach/commands/test_runner.py +141 -0
  19. atdd/coach/commands/tests/__init__.py +1 -0
  20. atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
  21. atdd/coach/commands/traceability.py +4264 -0
  22. atdd/coach/conventions/session.convention.yaml +754 -0
  23. atdd/coach/overlays/__init__.py +2 -0
  24. atdd/coach/overlays/claude.md +2 -0
  25. atdd/coach/schemas/config.schema.json +34 -0
  26. atdd/coach/schemas/manifest.schema.json +101 -0
  27. atdd/coach/templates/ATDD.md +282 -0
  28. atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
  29. atdd/coach/utils/__init__.py +0 -0
  30. atdd/coach/utils/graph/__init__.py +0 -0
  31. atdd/coach/utils/graph/urn.py +875 -0
  32. atdd/coach/validators/__init__.py +0 -0
  33. atdd/coach/validators/shared_fixtures.py +365 -0
  34. atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
  35. atdd/coach/validators/test_registry.py +575 -0
  36. atdd/coach/validators/test_session_validation.py +1183 -0
  37. atdd/coach/validators/test_traceability.py +448 -0
  38. atdd/coach/validators/test_update_feature_paths.py +108 -0
  39. atdd/coach/validators/test_validate_contract_consumers.py +297 -0
  40. atdd/coder/__init__.py +1 -0
  41. atdd/coder/conventions/adapter.recipe.yaml +88 -0
  42. atdd/coder/conventions/backend.convention.yaml +460 -0
  43. atdd/coder/conventions/boundaries.convention.yaml +666 -0
  44. atdd/coder/conventions/commons.convention.yaml +460 -0
  45. atdd/coder/conventions/complexity.recipe.yaml +109 -0
  46. atdd/coder/conventions/component-naming.convention.yaml +178 -0
  47. atdd/coder/conventions/design.convention.yaml +327 -0
  48. atdd/coder/conventions/design.recipe.yaml +273 -0
  49. atdd/coder/conventions/dto.convention.yaml +660 -0
  50. atdd/coder/conventions/frontend.convention.yaml +542 -0
  51. atdd/coder/conventions/green.convention.yaml +1012 -0
  52. atdd/coder/conventions/presentation.convention.yaml +587 -0
  53. atdd/coder/conventions/refactor.convention.yaml +535 -0
  54. atdd/coder/conventions/technology.convention.yaml +206 -0
  55. atdd/coder/conventions/tests/__init__.py +0 -0
  56. atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
  57. atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
  58. atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
  59. atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
  60. atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
  61. atdd/coder/conventions/thinness.recipe.yaml +82 -0
  62. atdd/coder/conventions/train.convention.yaml +325 -0
  63. atdd/coder/conventions/verification.protocol.yaml +53 -0
  64. atdd/coder/schemas/design_system.schema.json +361 -0
  65. atdd/coder/validators/__init__.py +0 -0
  66. atdd/coder/validators/test_commons_structure.py +485 -0
  67. atdd/coder/validators/test_complexity.py +416 -0
  68. atdd/coder/validators/test_cross_language_consistency.py +431 -0
  69. atdd/coder/validators/test_design_system_compliance.py +413 -0
  70. atdd/coder/validators/test_dto_testing_patterns.py +268 -0
  71. atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
  72. atdd/coder/validators/test_green_layer_dependencies.py +148 -0
  73. atdd/coder/validators/test_green_python_layer_structure.py +103 -0
  74. atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
  75. atdd/coder/validators/test_import_boundaries.py +396 -0
  76. atdd/coder/validators/test_init_file_urns.py +593 -0
  77. atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
  78. atdd/coder/validators/test_presentation_convention.py +260 -0
  79. atdd/coder/validators/test_python_architecture.py +674 -0
  80. atdd/coder/validators/test_quality_metrics.py +420 -0
  81. atdd/coder/validators/test_station_master_pattern.py +244 -0
  82. atdd/coder/validators/test_train_infrastructure.py +454 -0
  83. atdd/coder/validators/test_train_urns.py +293 -0
  84. atdd/coder/validators/test_typescript_architecture.py +616 -0
  85. atdd/coder/validators/test_usecase_structure.py +421 -0
  86. atdd/coder/validators/test_wagon_boundaries.py +586 -0
  87. atdd/conftest.py +126 -0
  88. atdd/planner/__init__.py +1 -0
  89. atdd/planner/conventions/acceptance.convention.yaml +538 -0
  90. atdd/planner/conventions/appendix.convention.yaml +187 -0
  91. atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
  92. atdd/planner/conventions/component.convention.yaml +670 -0
  93. atdd/planner/conventions/criteria.convention.yaml +141 -0
  94. atdd/planner/conventions/feature.convention.yaml +371 -0
  95. atdd/planner/conventions/interface.convention.yaml +382 -0
  96. atdd/planner/conventions/steps.convention.yaml +141 -0
  97. atdd/planner/conventions/train.convention.yaml +552 -0
  98. atdd/planner/conventions/wagon.convention.yaml +275 -0
  99. atdd/planner/conventions/wmbt.convention.yaml +258 -0
  100. atdd/planner/schemas/acceptance.schema.json +336 -0
  101. atdd/planner/schemas/appendix.schema.json +78 -0
  102. atdd/planner/schemas/component.schema.json +114 -0
  103. atdd/planner/schemas/feature.schema.json +197 -0
  104. atdd/planner/schemas/train.schema.json +192 -0
  105. atdd/planner/schemas/wagon.schema.json +281 -0
  106. atdd/planner/schemas/wmbt.schema.json +59 -0
  107. atdd/planner/validators/__init__.py +0 -0
  108. atdd/planner/validators/conftest.py +5 -0
  109. atdd/planner/validators/test_draft_wagon_registry.py +374 -0
  110. atdd/planner/validators/test_plan_cross_refs.py +240 -0
  111. atdd/planner/validators/test_plan_uniqueness.py +224 -0
  112. atdd/planner/validators/test_plan_urn_resolution.py +268 -0
  113. atdd/planner/validators/test_plan_wagons.py +174 -0
  114. atdd/planner/validators/test_train_validation.py +514 -0
  115. atdd/planner/validators/test_wagon_urn_chain.py +648 -0
  116. atdd/planner/validators/test_wmbt_consistency.py +327 -0
  117. atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
  118. atdd/tester/__init__.py +1 -0
  119. atdd/tester/conventions/artifact.convention.yaml +257 -0
  120. atdd/tester/conventions/contract.convention.yaml +1009 -0
  121. atdd/tester/conventions/filename.convention.yaml +555 -0
  122. atdd/tester/conventions/migration.convention.yaml +509 -0
  123. atdd/tester/conventions/red.convention.yaml +797 -0
  124. atdd/tester/conventions/routing.convention.yaml +51 -0
  125. atdd/tester/conventions/telemetry.convention.yaml +458 -0
  126. atdd/tester/schemas/a11y.tmpl.json +17 -0
  127. atdd/tester/schemas/artifact.schema.json +189 -0
  128. atdd/tester/schemas/contract.schema.json +591 -0
  129. atdd/tester/schemas/contract.tmpl.json +95 -0
  130. atdd/tester/schemas/db.tmpl.json +20 -0
  131. atdd/tester/schemas/e2e.tmpl.json +17 -0
  132. atdd/tester/schemas/edge_function.tmpl.json +17 -0
  133. atdd/tester/schemas/event.tmpl.json +17 -0
  134. atdd/tester/schemas/http.tmpl.json +19 -0
  135. atdd/tester/schemas/job.tmpl.json +18 -0
  136. atdd/tester/schemas/load.tmpl.json +21 -0
  137. atdd/tester/schemas/metric.tmpl.json +19 -0
  138. atdd/tester/schemas/pack.schema.json +139 -0
  139. atdd/tester/schemas/realtime.tmpl.json +20 -0
  140. atdd/tester/schemas/rls.tmpl.json +18 -0
  141. atdd/tester/schemas/script.tmpl.json +16 -0
  142. atdd/tester/schemas/sec.tmpl.json +18 -0
  143. atdd/tester/schemas/storage.tmpl.json +18 -0
  144. atdd/tester/schemas/telemetry.schema.json +128 -0
  145. atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
  146. atdd/tester/schemas/test_filename.schema.json +194 -0
  147. atdd/tester/schemas/test_intent.schema.json +179 -0
  148. atdd/tester/schemas/unit.tmpl.json +18 -0
  149. atdd/tester/schemas/visual.tmpl.json +18 -0
  150. atdd/tester/schemas/ws.tmpl.json +17 -0
  151. atdd/tester/utils/__init__.py +0 -0
  152. atdd/tester/utils/filename.py +300 -0
  153. atdd/tester/validators/__init__.py +0 -0
  154. atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
  155. atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
  156. atdd/tester/validators/conftest.py +5 -0
  157. atdd/tester/validators/coverage_gap_report.py +321 -0
  158. atdd/tester/validators/fix_dual_ac_references.py +179 -0
  159. atdd/tester/validators/remove_duplicate_lines.py +93 -0
  160. atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
  161. atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
  162. atdd/tester/validators/test_artifact_naming_category.py +307 -0
  163. atdd/tester/validators/test_contract_schema_compliance.py +706 -0
  164. atdd/tester/validators/test_contracts_structure.py +200 -0
  165. atdd/tester/validators/test_coverage_adequacy.py +797 -0
  166. atdd/tester/validators/test_dual_ac_reference.py +225 -0
  167. atdd/tester/validators/test_fixture_validity.py +372 -0
  168. atdd/tester/validators/test_isolation.py +487 -0
  169. atdd/tester/validators/test_migration_coverage.py +204 -0
  170. atdd/tester/validators/test_migration_criteria.py +276 -0
  171. atdd/tester/validators/test_migration_generation.py +116 -0
  172. atdd/tester/validators/test_python_test_naming.py +410 -0
  173. atdd/tester/validators/test_red_layer_validation.py +95 -0
  174. atdd/tester/validators/test_red_python_layer_structure.py +87 -0
  175. atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
  176. atdd/tester/validators/test_telemetry_structure.py +634 -0
  177. atdd/tester/validators/test_typescript_test_naming.py +301 -0
  178. atdd/tester/validators/test_typescript_test_structure.py +84 -0
  179. atdd-0.2.1.dist-info/METADATA +221 -0
  180. atdd-0.2.1.dist-info/RECORD +184 -0
  181. atdd-0.2.1.dist-info/WHEEL +5 -0
  182. atdd-0.2.1.dist-info/entry_points.txt +2 -0
  183. atdd-0.2.1.dist-info/licenses/LICENSE +674 -0
  184. atdd-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,289 @@
1
+ """
2
+ Tests for complexity recipe.
3
+
4
+ Tests SPEC-CODER-UTL-0158 to 047, 054, 056, 057, 058
5
+ ATDD: These tests define expected behavior of complexity recipe BEFORE implementation.
6
+ """
7
+ import pytest
8
+ import yaml
9
+ from pathlib import Path
10
+ from typing import Dict, Any
11
+
12
+
13
+ # Recipe loader utilities (shared with thinness tests)
14
+ def load_recipe(recipe_name: str) -> Dict[str, Any]:
15
+ """
16
+ Load recipe YAML file.
17
+
18
+ SPEC-CODER-UTL-0158: Load complexity recipe
19
+ """
20
+ recipe_path = Path(__file__).resolve().parents[1] / f"{recipe_name}.recipe.yaml"
21
+
22
+ if not recipe_path.exists():
23
+ raise FileNotFoundError(f"Recipe not found: {recipe_path}")
24
+
25
+ with open(recipe_path, 'r') as f:
26
+ return yaml.safe_load(f)
27
+
28
+
29
+ def check_recipe_applies(recipe_name: str, smells: Dict[str, Any]) -> bool:
30
+ """
31
+ Check if recipe applies based on smell detection results.
32
+
33
+ SPEC-CODER-UTL-0159: Detect when complexity recipe applies
34
+ """
35
+ recipe = load_recipe(recipe_name)
36
+
37
+ if recipe_name == "complexity":
38
+ # complexity applies when complexity check fails
39
+ return smells.get("complexity", {}).get("passed", True) is False
40
+
41
+ return False
42
+
43
+
44
+ def execute_recipe_step(recipe_name: str, step: int, context: Dict[str, Any]) -> Dict[str, Any]:
45
+ """
46
+ Execute a recipe step.
47
+
48
+ SPEC-CODER-UTL-0160, 046, 047: Execute steps 1, 2, 3
49
+ """
50
+ recipe = load_recipe(recipe_name)
51
+ steps = recipe.get("steps", [])
52
+
53
+ if step < 1 or step > len(steps):
54
+ return {"success": False, "error": f"Invalid step: {step}"}
55
+
56
+ step_def = steps[step - 1]
57
+
58
+ # Return step guidance
59
+ return {
60
+ "success": True,
61
+ "step": step,
62
+ "what": step_def.get("what", ""),
63
+ "where": step_def.get("where", ""),
64
+ "template": step_def.get("template", ""),
65
+ "example": step_def.get("example", ""),
66
+ "next_action": "continue" if step < len(steps) else "verify_final"
67
+ }
68
+
69
+
70
+ def select_recipe(smells: Dict[str, Any]) -> Dict[str, Any]:
71
+ """
72
+ Select recipe based on smell detection.
73
+
74
+ SPEC-CODER-UTL-0169: Map complexity smell to complexity recipe
75
+ """
76
+ # Priority 1: Handler smell → thinness
77
+ if smells.get("thinness", {}).get("passed", True) is False:
78
+ return {"recipe": "thinness", "priority": 1}
79
+
80
+ # Priority 2: Complexity smell → complexity
81
+ if smells.get("complexity", {}).get("passed", True) is False:
82
+ return {"recipe": "complexity", "priority": 2}
83
+
84
+ # Priority 3: Missing adapter → adapter
85
+ if smells.get("missing_adapter", False):
86
+ return {"recipe": "adapter", "priority": 3}
87
+
88
+ return {"recipe": None, "priority": 0}
89
+
90
+
91
+ def verify_recipe_step(step_result: Dict[str, Any], test_status: str) -> bool:
92
+ """
93
+ Verify recipe step maintains GREEN tests.
94
+
95
+ SPEC-CODER-UTL-0171: Verify recipe step maintains GREEN tests
96
+ SPEC-CODER-UTL-0173: Rollback on step failure
97
+ """
98
+ if test_status == "GREEN":
99
+ return True
100
+
101
+ # RED tests trigger rollback
102
+ if test_status == "RED":
103
+ # This would call rollback_refactor_step() in real usage
104
+ return False
105
+
106
+ return False
107
+
108
+
109
+ def verify_recipe_final(recipe_name: str, results: Dict[str, Any]) -> Dict[str, Any]:
110
+ """
111
+ Verify final state after all recipe steps.
112
+
113
+ SPEC-CODER-UTL-0172: Verify final state after all recipe steps
114
+ """
115
+ verification = {"success": True, "checks": []}
116
+
117
+ if recipe_name == "complexity":
118
+ # Final verification: complexity.check() should pass
119
+ verification["checks"].append({
120
+ "check": "complexity.check()",
121
+ "expected": "passed=true"
122
+ })
123
+ verification["complexity_reduced"] = True
124
+
125
+ return verification
126
+
127
+
128
+ # TESTS
129
+
130
+ class TestSpecificationRecipeLoading:
131
+ """Test SPEC-CODER-UTL-0158: Load complexity recipe"""
132
+
133
+ def test_load_recipe(self):
134
+ """Should load complexity recipe YAML"""
135
+ recipe = load_recipe("complexity")
136
+
137
+ assert recipe is not None
138
+ assert recipe["recipe"] == "complexity"
139
+ assert recipe["pattern"] == "Complexity Reduction (Split/Extract)"
140
+ assert "steps" in recipe
141
+ assert len(recipe["steps"]) == 3
142
+
143
+
144
+ class TestSpecificationRecipeApplies:
145
+ """Test SPEC-CODER-UTL-0159: Detect when complexity recipe applies"""
146
+
147
+ def test_recipe_applies(self):
148
+ """Should return true when complexity check fails"""
149
+ smells = {
150
+ "complexity": {
151
+ "passed": False,
152
+ "violations": [
153
+ {"function": "calculateDiscount", "complexity": 12, "line": 45}
154
+ ]
155
+ }
156
+ }
157
+
158
+ result = check_recipe_applies("complexity", smells)
159
+
160
+ assert result is True
161
+
162
+ def test_recipe_not_applies(self):
163
+ """Should return false when complexity check passes"""
164
+ smells = {
165
+ "complexity": {
166
+ "passed": True,
167
+ "violations": []
168
+ }
169
+ }
170
+
171
+ result = check_recipe_applies("complexity", smells)
172
+
173
+ assert result is False
174
+
175
+
176
+ class TestSpecificationRecipeSteps:
177
+ """Test SPEC-CODER-UTL-0160, 046, 047: Execute recipe steps"""
178
+
179
+ def test_execute_step_1(self):
180
+ """Should execute step 1: create base complexity"""
181
+ context = {"file_path": "domain/order.py"}
182
+
183
+ result = execute_recipe_step("complexity", 1, context)
184
+
185
+ assert result["success"] is True
186
+ assert "complexity" in result["what"].lower() or "base" in result["what"].lower()
187
+ assert "domain/complexity/" in result["where"]
188
+ assert "is_satisfied_by" in result["template"] or result["example"]
189
+
190
+ def test_execute_step_2(self):
191
+ """Should execute step 2: extract complexitys"""
192
+ context = {"base_created": True}
193
+
194
+ result = execute_recipe_step("complexity", 2, context)
195
+
196
+ assert result["success"] is True
197
+ assert "extract" in result["what"].lower() or "boolean" in result["what"].lower()
198
+ assert "domain/complexity/" in result["where"]
199
+
200
+ def test_execute_step_3(self):
201
+ """Should execute step 3: compose complexitys"""
202
+ context = {"base_created": True, "specs_extracted": True}
203
+
204
+ result = execute_recipe_step("complexity", 3, context)
205
+
206
+ assert result["success"] is True
207
+ assert "compose" in result["what"].lower() or "and_" in result["template"] or "or_" in result["template"]
208
+ assert result["next_action"] == "verify_final"
209
+
210
+
211
+ class TestSpecificationRecipeSelection:
212
+ """Test SPEC-CODER-UTL-0169: Map complexity smell to complexity recipe"""
213
+
214
+ def test_select_from_smell(self):
215
+ """Should select complexity recipe when complexity smell detected"""
216
+ smells = {
217
+ "complexity": {
218
+ "passed": False,
219
+ "violations": [{"function": "process", "complexity": 15}]
220
+ }
221
+ }
222
+
223
+ result = select_recipe(smells)
224
+
225
+ assert result["recipe"] == "complexity"
226
+ assert result["priority"] == 2
227
+
228
+
229
+ class TestSpecificationRecipeVerification:
230
+ """Test SPEC-CODER-UTL-0171, 057, 058: Recipe verification"""
231
+
232
+ def test_verify_step_green(self):
233
+ """Should return true when tests are GREEN"""
234
+ step_result = {"success": True, "step": 1}
235
+
236
+ result = verify_recipe_step(step_result, "GREEN")
237
+
238
+ assert result is True
239
+
240
+ def test_rollback_on_failure(self):
241
+ """Should return false and trigger rollback when tests are RED"""
242
+ step_result = {"success": True, "step": 1}
243
+
244
+ result = verify_recipe_step(step_result, "RED")
245
+
246
+ assert result is False
247
+
248
+ def test_verify_final(self):
249
+ """Should verify final state with complexity check"""
250
+ results = {"all_steps_completed": True}
251
+
252
+ verification = verify_recipe_final("complexity", results)
253
+
254
+ assert verification["success"] is True
255
+ assert verification["complexity_reduced"] is True
256
+ assert len(verification["checks"]) > 0
257
+ assert any("complexity" in check["check"] for check in verification["checks"])
258
+
259
+
260
+ class TestSpecificationRecipeIntegration:
261
+ """Integration tests for full recipe workflow"""
262
+
263
+ def test_full_recipe_workflow(self):
264
+ """Should execute full recipe from detection to verification"""
265
+ # 1. Detect smell
266
+ smells = {"complexity": {"passed": False, "violations": [{"function": "foo", "complexity": 12}]}}
267
+
268
+ # 2. Select recipe
269
+ selected = select_recipe(smells)
270
+ assert selected["recipe"] == "complexity"
271
+
272
+ # 3. Load recipe
273
+ recipe = load_recipe("complexity")
274
+ assert recipe is not None
275
+
276
+ # 4. Execute steps
277
+ context = {}
278
+ for step in range(1, 4):
279
+ result = execute_recipe_step("complexity", step, context)
280
+ assert result["success"] is True
281
+
282
+ # Verify step
283
+ verified = verify_recipe_step(result, "GREEN")
284
+ assert verified is True
285
+
286
+ # 5. Final verification
287
+ verification = verify_recipe_final("complexity", {"all_steps": True})
288
+ assert verification["success"] is True
289
+ assert verification["complexity_reduced"] is True
@@ -0,0 +1,278 @@
1
+ """
2
+ Tests for component taxonomy adoption in conventions.
3
+
4
+ Tests SPEC-CODER-CONV-0006 through SPEC-CODER-CONV-0011
5
+ ATDD: Validates that conventions use generic component types with examples.
6
+ """
7
+ import pytest
8
+ import yaml
9
+ from pathlib import Path
10
+ from typing import Dict, Any, List
11
+
12
+
13
+ # Utility functions for loading YAML files
14
+ def load_yaml(file_path: Path) -> Dict[str, Any]:
15
+ """Load and parse a YAML file."""
16
+ with open(file_path, 'r') as f:
17
+ return yaml.safe_load(f)
18
+
19
+
20
+ def get_project_root() -> Path:
21
+ """Get the project root directory."""
22
+ # From tests/ directory, go up 4 levels: tests -> conventions -> coder -> atdd -> root
23
+ return Path(__file__).parent.parent.parent.parent.parent
24
+
25
+
26
+ def load_convention_backend() -> Dict[str, Any]:
27
+ """Load the backend convention."""
28
+ conventions_dir = Path(__file__).parent.parent
29
+ path = conventions_dir / "backend.convention.yaml"
30
+ return load_yaml(path)
31
+
32
+
33
+ def load_convention_frontend() -> Dict[str, Any]:
34
+ """Load the frontend convention."""
35
+ conventions_dir = Path(__file__).parent.parent
36
+ path = conventions_dir / "frontend.convention.yaml"
37
+ return load_yaml(path)
38
+
39
+
40
+ def get_component_type_names(layer_data: Dict[str, Any]) -> List[str]:
41
+ """Extract component type names from a layer."""
42
+ component_types = layer_data.get("component_types", [])
43
+ return [ct["name"] for ct in component_types]
44
+
45
+
46
+ # Expected generic component types for validation
47
+ BACKEND_GENERIC_TYPES = {
48
+ "presentation": ["controllers", "routes", "serializers", "validators", "middleware", "guards", "views"],
49
+ "application": ["use_cases", "handlers", "ports", "dtos", "policies", "workflows"],
50
+ "domain": ["entities", "value_objects", "aggregates", "services", "specifications", "events", "exceptions"],
51
+ "integration": ["repositories", "clients", "caches", "engines", "formatters", "notifiers", "queues", "stores", "mappers", "schedulers", "monitors"]
52
+ }
53
+
54
+ FRONTEND_GENERIC_TYPES = {
55
+ "presentation": ["views", "components", "containers", "controllers", "routes", "layouts", "styles", "animations", "forms", "hooks", "directives", "filters"],
56
+ "application": ["use_cases", "ports", "policies", "dtos"],
57
+ "domain": ["entities", "value_objects", "services", "specifications", "exceptions"],
58
+ "integration": ["repositories", "clients", "stores", "serializers", "mappers", "interceptors", "caches", "synchronizers", "monitors", "validators", "workers", "connectors"]
59
+ }
60
+
61
+
62
+ class TestBackendConventionAdoptsTaxonomy:
63
+ """Test SPEC-CODER-CONV-0006: Backend convention uses generic component types"""
64
+
65
+ def test_backend_convention_adopts_taxonomy_types(self):
66
+ """Should have generic component types with examples for all 4 layers"""
67
+ convention = load_convention_backend()
68
+ backend = convention.get("backend", {})
69
+ layers = backend.get("layers", {})
70
+
71
+ for layer_name, expected_types in BACKEND_GENERIC_TYPES.items():
72
+ layer = layers.get(layer_name, {})
73
+ actual_types = get_component_type_names(layer)
74
+
75
+ # Verify all generic types are present
76
+ for expected_type in expected_types:
77
+ assert expected_type in actual_types, \
78
+ f"{layer_name} layer missing generic type '{expected_type}'"
79
+
80
+ # Verify each component type has required fields
81
+ component_types = layer.get("component_types", [])
82
+ for ct in component_types:
83
+ assert "name" in ct, f"Component type missing 'name' field"
84
+ assert "description" in ct, f"Component type {ct.get('name')} missing 'description'"
85
+ assert "suffix" in ct, f"Component type {ct.get('name')} missing 'suffix'"
86
+ assert "examples" in ct, f"Component type {ct.get('name')} missing 'examples'"
87
+ assert len(ct["examples"]) > 0, f"Component type {ct.get('name')} has empty examples"
88
+
89
+
90
+ class TestFrontendConventionAdoptsTaxonomy:
91
+ """Test SPEC-CODER-CONV-0007: Frontend convention uses generic component types"""
92
+
93
+ def test_frontend_convention_adopts_taxonomy_types(self):
94
+ """Should have generic component types with examples for all 4 layers"""
95
+ convention = load_convention_frontend()
96
+ frontend = convention.get("frontend", {})
97
+ layers = frontend.get("layers", {})
98
+
99
+ for layer_name, expected_types in FRONTEND_GENERIC_TYPES.items():
100
+ layer = layers.get(layer_name, {})
101
+ actual_types = get_component_type_names(layer)
102
+
103
+ # Verify all generic types are present
104
+ for expected_type in expected_types:
105
+ assert expected_type in actual_types, \
106
+ f"{layer_name} layer missing generic type '{expected_type}'"
107
+
108
+ # Verify each component type has required fields
109
+ component_types = layer.get("component_types", [])
110
+ for ct in component_types:
111
+ assert "name" in ct, f"Component type missing 'name' field"
112
+ assert "description" in ct, f"Component type {ct.get('name')} missing 'description'"
113
+ assert "suffix" in ct, f"Component type {ct.get('name')} missing 'suffix'"
114
+ assert "examples" in ct, f"Component type {ct.get('name')} missing 'examples'"
115
+ assert len(ct["examples"]) > 0, f"Component type {ct.get('name')} has empty examples"
116
+
117
+
118
+ class TestBackendPreservesDependencyRules:
119
+ """Test SPEC-CODER-CONV-0008: Backend convention preserves dependency rules and CI enforcement"""
120
+
121
+ def test_backend_preserves_dependency_rules(self):
122
+ """Should preserve dependency.allowed_edges, forbidden_examples, and ci_enforcement sections"""
123
+ convention = load_convention_backend()
124
+ backend = convention.get("backend", {})
125
+
126
+ # Verify dependency section exists
127
+ dependency = backend.get("dependency", {})
128
+ assert dependency is not None, "Backend dependency section missing"
129
+
130
+ # Verify allowed_edges preserved
131
+ allowed_edges = dependency.get("allowed_edges", [])
132
+ assert len(allowed_edges) > 0, "Backend allowed_edges empty or missing"
133
+
134
+ # Check specific expected edges
135
+ edge_from_values = [edge.get("from") for edge in allowed_edges]
136
+ assert "presentation" in edge_from_values, "Missing presentation edge"
137
+ assert "application" in edge_from_values, "Missing application edge"
138
+ assert "integration" in edge_from_values, "Missing integration edge"
139
+
140
+ # Verify forbidden_examples preserved
141
+ forbidden_examples = dependency.get("forbidden_examples", [])
142
+ assert len(forbidden_examples) > 0, "Backend forbidden_examples empty or missing"
143
+
144
+ # Verify ci_enforcement section exists
145
+ ci_enforcement = backend.get("ci_enforcement", {})
146
+ assert ci_enforcement is not None, "Backend ci_enforcement section missing"
147
+
148
+ # Verify ci_enforcement has required subsections
149
+ checks = ci_enforcement.get("checks", [])
150
+ assert len(checks) > 0, "Backend ci_enforcement.checks empty or missing"
151
+
152
+ tools = ci_enforcement.get("tools", [])
153
+ assert len(tools) > 0, "Backend ci_enforcement.tools empty or missing"
154
+
155
+ failure_policy = ci_enforcement.get("failure_policy")
156
+ assert failure_policy is not None, "Backend ci_enforcement.failure_policy missing"
157
+
158
+
159
+ class TestFrontendPreservesDependencyRules:
160
+ """Test SPEC-CODER-CONV-0009: Frontend convention preserves dependency rules and CI enforcement"""
161
+
162
+ def test_frontend_preserves_dependency_rules(self):
163
+ """Should preserve dependency.allowed_edges, forbidden_examples, and ci_enforcement sections"""
164
+ convention = load_convention_frontend()
165
+ frontend = convention.get("frontend", {})
166
+
167
+ # Verify dependency section exists
168
+ dependency = frontend.get("dependency", {})
169
+ assert dependency is not None, "Frontend dependency section missing"
170
+
171
+ # Verify allowed_edges preserved
172
+ allowed_edges = dependency.get("allowed_edges", [])
173
+ assert len(allowed_edges) > 0, "Frontend allowed_edges empty or missing"
174
+
175
+ # Check specific expected edges
176
+ edge_from_values = [edge.get("from") for edge in allowed_edges]
177
+ assert "presentation" in edge_from_values, "Missing presentation edge"
178
+ assert "application" in edge_from_values, "Missing application edge"
179
+ assert "integration" in edge_from_values, "Missing integration edge"
180
+
181
+ # Verify forbidden_examples preserved
182
+ forbidden_examples = dependency.get("forbidden_examples", [])
183
+ assert len(forbidden_examples) > 0, "Frontend forbidden_examples empty or missing"
184
+
185
+ # Verify ci_enforcement section exists
186
+ ci_enforcement = frontend.get("ci_enforcement", {})
187
+ assert ci_enforcement is not None, "Frontend ci_enforcement section missing"
188
+
189
+ # Verify ci_enforcement has required subsections
190
+ checks = ci_enforcement.get("checks", [])
191
+ assert len(checks) > 0, "Frontend ci_enforcement.checks empty or missing"
192
+
193
+ tools = ci_enforcement.get("tools", [])
194
+ assert len(tools) > 0, "Frontend ci_enforcement.tools empty or missing"
195
+
196
+ failure_policy = ci_enforcement.get("failure_policy")
197
+ assert failure_policy is not None, "Frontend ci_enforcement.failure_policy missing"
198
+
199
+
200
+ class TestTaxonomyUsesGenericNames:
201
+ """Test SPEC-CODER-CONV-0010: Component types have generic names with example arrays"""
202
+
203
+ def test_taxonomy_uses_generic_names_with_examples(self):
204
+ """Should verify conventions use generic names with examples arrays"""
205
+ backend_convention = load_convention_backend()
206
+ frontend_convention = load_convention_frontend()
207
+
208
+ # Test backend convention
209
+ backend = backend_convention.get("backend", {})
210
+ layers = backend.get("layers", {})
211
+ integration_layer = layers.get("integration", {})
212
+ component_types = integration_layer.get("component_types", [])
213
+
214
+ # Verify generic component types exist with examples
215
+ generic_types = ["repositories", "engines", "formatters", "clients", "caches"]
216
+ found_types = {ct["name"] for ct in component_types}
217
+
218
+ for generic_type in generic_types:
219
+ assert generic_type in found_types, f"Generic type '{generic_type}' not found in backend convention"
220
+
221
+ # Find the component type and verify it has examples
222
+ ct = next((ct for ct in component_types if ct["name"] == generic_type), None)
223
+ assert ct is not None, f"Component type {generic_type} not found"
224
+ assert "examples" in ct, f"Component type {generic_type} missing examples array"
225
+ assert len(ct["examples"]) > 0, f"Component type {generic_type} has empty examples array"
226
+ assert "description" in ct, f"Component type {generic_type} missing description"
227
+
228
+ # Test frontend convention
229
+ frontend = frontend_convention.get("frontend", {})
230
+ layers = frontend.get("layers", {})
231
+ presentation_layer = layers.get("presentation", {})
232
+ component_types = presentation_layer.get("component_types", [])
233
+
234
+ # Verify generic component types exist with examples
235
+ generic_types = ["views", "components", "controllers"]
236
+ found_types = {ct["name"] for ct in component_types}
237
+
238
+ for generic_type in generic_types:
239
+ assert generic_type in found_types, f"Generic type '{generic_type}' not found in frontend convention"
240
+
241
+ # Find the component type and verify it has examples
242
+ ct = next((ct for ct in component_types if ct["name"] == generic_type), None)
243
+ assert ct is not None, f"Component type {generic_type} not found"
244
+ assert "examples" in ct, f"Component type {generic_type} missing examples array"
245
+ assert len(ct["examples"]) > 0, f"Component type {generic_type} has empty examples array"
246
+ assert "description" in ct, f"Component type {generic_type} missing description"
247
+
248
+
249
+ class TestConventionsExcludeUrnAndOrganization:
250
+ """Test SPEC-CODER-CONV-0011: Conventions exclude URN and organization sections"""
251
+
252
+ def test_conventions_exclude_urn_and_organization(self):
253
+ """Should verify conventions don't have urn_pattern, urn_examples, organization, or framework_adaptations"""
254
+ backend_convention = load_convention_backend()
255
+ frontend_convention = load_convention_frontend()
256
+
257
+ # Test backend convention
258
+ assert "urn_pattern" not in backend_convention, "Backend convention should not have urn_pattern"
259
+ assert "urn_examples" not in backend_convention, "Backend convention should not have urn_examples"
260
+ assert "organization" not in backend_convention, "Backend convention should not have organization section"
261
+ assert "framework_adaptations" not in backend_convention, "Backend convention should not have framework_adaptations"
262
+
263
+ # Test frontend convention
264
+ assert "urn_pattern" not in frontend_convention, "Frontend convention should not have urn_pattern"
265
+ assert "urn_examples" not in frontend_convention, "Frontend convention should not have urn_examples"
266
+ assert "organization" not in frontend_convention, "Frontend convention should not have organization section"
267
+ assert "framework_adaptations" not in frontend_convention, "Frontend convention should not have framework_adaptations"
268
+
269
+ # Verify conventions only have expected top-level keys
270
+ expected_backend_keys = {"version", "name", "description", "backend"}
271
+ actual_backend_keys = set(backend_convention.keys())
272
+ assert actual_backend_keys == expected_backend_keys, \
273
+ f"Backend convention has unexpected keys: {actual_backend_keys - expected_backend_keys}"
274
+
275
+ expected_frontend_keys = {"version", "name", "description", "frontend"}
276
+ actual_frontend_keys = set(frontend_convention.keys())
277
+ assert actual_frontend_keys == expected_frontend_keys, \
278
+ f"Frontend convention has unexpected keys: {actual_frontend_keys - expected_frontend_keys}"