atdd 0.1.0__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 (183) hide show
  1. atdd/__init__.py +0 -0
  2. atdd/cli.py +404 -0
  3. atdd/coach/__init__.py +0 -0
  4. atdd/coach/commands/__init__.py +0 -0
  5. atdd/coach/commands/add_persistence_metadata.py +215 -0
  6. atdd/coach/commands/analyze_migrations.py +188 -0
  7. atdd/coach/commands/consumers.py +720 -0
  8. atdd/coach/commands/infer_governance_status.py +149 -0
  9. atdd/coach/commands/initializer.py +177 -0
  10. atdd/coach/commands/interface.py +1078 -0
  11. atdd/coach/commands/inventory.py +565 -0
  12. atdd/coach/commands/migration.py +240 -0
  13. atdd/coach/commands/registry.py +1560 -0
  14. atdd/coach/commands/session.py +430 -0
  15. atdd/coach/commands/sync.py +405 -0
  16. atdd/coach/commands/test_interface.py +399 -0
  17. atdd/coach/commands/test_runner.py +141 -0
  18. atdd/coach/commands/tests/__init__.py +1 -0
  19. atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
  20. atdd/coach/commands/traceability.py +4264 -0
  21. atdd/coach/conventions/session.convention.yaml +754 -0
  22. atdd/coach/overlays/__init__.py +2 -0
  23. atdd/coach/overlays/claude.md +2 -0
  24. atdd/coach/schemas/config.schema.json +34 -0
  25. atdd/coach/schemas/manifest.schema.json +101 -0
  26. atdd/coach/templates/ATDD.md +282 -0
  27. atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
  28. atdd/coach/utils/__init__.py +0 -0
  29. atdd/coach/utils/graph/__init__.py +0 -0
  30. atdd/coach/utils/graph/urn.py +875 -0
  31. atdd/coach/validators/__init__.py +0 -0
  32. atdd/coach/validators/shared_fixtures.py +365 -0
  33. atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
  34. atdd/coach/validators/test_registry.py +575 -0
  35. atdd/coach/validators/test_session_validation.py +1183 -0
  36. atdd/coach/validators/test_traceability.py +448 -0
  37. atdd/coach/validators/test_update_feature_paths.py +108 -0
  38. atdd/coach/validators/test_validate_contract_consumers.py +297 -0
  39. atdd/coder/__init__.py +1 -0
  40. atdd/coder/conventions/adapter.recipe.yaml +88 -0
  41. atdd/coder/conventions/backend.convention.yaml +460 -0
  42. atdd/coder/conventions/boundaries.convention.yaml +666 -0
  43. atdd/coder/conventions/commons.convention.yaml +460 -0
  44. atdd/coder/conventions/complexity.recipe.yaml +109 -0
  45. atdd/coder/conventions/component-naming.convention.yaml +178 -0
  46. atdd/coder/conventions/design.convention.yaml +327 -0
  47. atdd/coder/conventions/design.recipe.yaml +273 -0
  48. atdd/coder/conventions/dto.convention.yaml +660 -0
  49. atdd/coder/conventions/frontend.convention.yaml +542 -0
  50. atdd/coder/conventions/green.convention.yaml +1012 -0
  51. atdd/coder/conventions/presentation.convention.yaml +587 -0
  52. atdd/coder/conventions/refactor.convention.yaml +535 -0
  53. atdd/coder/conventions/technology.convention.yaml +206 -0
  54. atdd/coder/conventions/tests/__init__.py +0 -0
  55. atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
  56. atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
  57. atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
  58. atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
  59. atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
  60. atdd/coder/conventions/thinness.recipe.yaml +82 -0
  61. atdd/coder/conventions/train.convention.yaml +325 -0
  62. atdd/coder/conventions/verification.protocol.yaml +53 -0
  63. atdd/coder/schemas/design_system.schema.json +361 -0
  64. atdd/coder/validators/__init__.py +0 -0
  65. atdd/coder/validators/test_commons_structure.py +485 -0
  66. atdd/coder/validators/test_complexity.py +416 -0
  67. atdd/coder/validators/test_cross_language_consistency.py +431 -0
  68. atdd/coder/validators/test_design_system_compliance.py +413 -0
  69. atdd/coder/validators/test_dto_testing_patterns.py +268 -0
  70. atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
  71. atdd/coder/validators/test_green_layer_dependencies.py +148 -0
  72. atdd/coder/validators/test_green_python_layer_structure.py +103 -0
  73. atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
  74. atdd/coder/validators/test_import_boundaries.py +396 -0
  75. atdd/coder/validators/test_init_file_urns.py +593 -0
  76. atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
  77. atdd/coder/validators/test_presentation_convention.py +260 -0
  78. atdd/coder/validators/test_python_architecture.py +674 -0
  79. atdd/coder/validators/test_quality_metrics.py +420 -0
  80. atdd/coder/validators/test_station_master_pattern.py +244 -0
  81. atdd/coder/validators/test_train_infrastructure.py +454 -0
  82. atdd/coder/validators/test_train_urns.py +293 -0
  83. atdd/coder/validators/test_typescript_architecture.py +616 -0
  84. atdd/coder/validators/test_usecase_structure.py +421 -0
  85. atdd/coder/validators/test_wagon_boundaries.py +586 -0
  86. atdd/conftest.py +126 -0
  87. atdd/planner/__init__.py +1 -0
  88. atdd/planner/conventions/acceptance.convention.yaml +538 -0
  89. atdd/planner/conventions/appendix.convention.yaml +187 -0
  90. atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
  91. atdd/planner/conventions/component.convention.yaml +670 -0
  92. atdd/planner/conventions/criteria.convention.yaml +141 -0
  93. atdd/planner/conventions/feature.convention.yaml +371 -0
  94. atdd/planner/conventions/interface.convention.yaml +382 -0
  95. atdd/planner/conventions/steps.convention.yaml +141 -0
  96. atdd/planner/conventions/train.convention.yaml +552 -0
  97. atdd/planner/conventions/wagon.convention.yaml +275 -0
  98. atdd/planner/conventions/wmbt.convention.yaml +258 -0
  99. atdd/planner/schemas/acceptance.schema.json +336 -0
  100. atdd/planner/schemas/appendix.schema.json +78 -0
  101. atdd/planner/schemas/component.schema.json +114 -0
  102. atdd/planner/schemas/feature.schema.json +197 -0
  103. atdd/planner/schemas/train.schema.json +192 -0
  104. atdd/planner/schemas/wagon.schema.json +281 -0
  105. atdd/planner/schemas/wmbt.schema.json +59 -0
  106. atdd/planner/validators/__init__.py +0 -0
  107. atdd/planner/validators/conftest.py +5 -0
  108. atdd/planner/validators/test_draft_wagon_registry.py +374 -0
  109. atdd/planner/validators/test_plan_cross_refs.py +240 -0
  110. atdd/planner/validators/test_plan_uniqueness.py +224 -0
  111. atdd/planner/validators/test_plan_urn_resolution.py +268 -0
  112. atdd/planner/validators/test_plan_wagons.py +174 -0
  113. atdd/planner/validators/test_train_validation.py +514 -0
  114. atdd/planner/validators/test_wagon_urn_chain.py +648 -0
  115. atdd/planner/validators/test_wmbt_consistency.py +327 -0
  116. atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
  117. atdd/tester/__init__.py +1 -0
  118. atdd/tester/conventions/artifact.convention.yaml +257 -0
  119. atdd/tester/conventions/contract.convention.yaml +1009 -0
  120. atdd/tester/conventions/filename.convention.yaml +555 -0
  121. atdd/tester/conventions/migration.convention.yaml +509 -0
  122. atdd/tester/conventions/red.convention.yaml +797 -0
  123. atdd/tester/conventions/routing.convention.yaml +51 -0
  124. atdd/tester/conventions/telemetry.convention.yaml +458 -0
  125. atdd/tester/schemas/a11y.tmpl.json +17 -0
  126. atdd/tester/schemas/artifact.schema.json +189 -0
  127. atdd/tester/schemas/contract.schema.json +591 -0
  128. atdd/tester/schemas/contract.tmpl.json +95 -0
  129. atdd/tester/schemas/db.tmpl.json +20 -0
  130. atdd/tester/schemas/e2e.tmpl.json +17 -0
  131. atdd/tester/schemas/edge_function.tmpl.json +17 -0
  132. atdd/tester/schemas/event.tmpl.json +17 -0
  133. atdd/tester/schemas/http.tmpl.json +19 -0
  134. atdd/tester/schemas/job.tmpl.json +18 -0
  135. atdd/tester/schemas/load.tmpl.json +21 -0
  136. atdd/tester/schemas/metric.tmpl.json +19 -0
  137. atdd/tester/schemas/pack.schema.json +139 -0
  138. atdd/tester/schemas/realtime.tmpl.json +20 -0
  139. atdd/tester/schemas/rls.tmpl.json +18 -0
  140. atdd/tester/schemas/script.tmpl.json +16 -0
  141. atdd/tester/schemas/sec.tmpl.json +18 -0
  142. atdd/tester/schemas/storage.tmpl.json +18 -0
  143. atdd/tester/schemas/telemetry.schema.json +128 -0
  144. atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
  145. atdd/tester/schemas/test_filename.schema.json +194 -0
  146. atdd/tester/schemas/test_intent.schema.json +179 -0
  147. atdd/tester/schemas/unit.tmpl.json +18 -0
  148. atdd/tester/schemas/visual.tmpl.json +18 -0
  149. atdd/tester/schemas/ws.tmpl.json +17 -0
  150. atdd/tester/utils/__init__.py +0 -0
  151. atdd/tester/utils/filename.py +300 -0
  152. atdd/tester/validators/__init__.py +0 -0
  153. atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
  154. atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
  155. atdd/tester/validators/conftest.py +5 -0
  156. atdd/tester/validators/coverage_gap_report.py +321 -0
  157. atdd/tester/validators/fix_dual_ac_references.py +179 -0
  158. atdd/tester/validators/remove_duplicate_lines.py +93 -0
  159. atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
  160. atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
  161. atdd/tester/validators/test_artifact_naming_category.py +307 -0
  162. atdd/tester/validators/test_contract_schema_compliance.py +706 -0
  163. atdd/tester/validators/test_contracts_structure.py +200 -0
  164. atdd/tester/validators/test_coverage_adequacy.py +797 -0
  165. atdd/tester/validators/test_dual_ac_reference.py +225 -0
  166. atdd/tester/validators/test_fixture_validity.py +372 -0
  167. atdd/tester/validators/test_isolation.py +487 -0
  168. atdd/tester/validators/test_migration_coverage.py +204 -0
  169. atdd/tester/validators/test_migration_criteria.py +276 -0
  170. atdd/tester/validators/test_migration_generation.py +116 -0
  171. atdd/tester/validators/test_python_test_naming.py +410 -0
  172. atdd/tester/validators/test_red_layer_validation.py +95 -0
  173. atdd/tester/validators/test_red_python_layer_structure.py +87 -0
  174. atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
  175. atdd/tester/validators/test_telemetry_structure.py +634 -0
  176. atdd/tester/validators/test_typescript_test_naming.py +301 -0
  177. atdd/tester/validators/test_typescript_test_structure.py +84 -0
  178. atdd-0.1.0.dist-info/METADATA +191 -0
  179. atdd-0.1.0.dist-info/RECORD +183 -0
  180. atdd-0.1.0.dist-info/WHEEL +5 -0
  181. atdd-0.1.0.dist-info/entry_points.txt +2 -0
  182. atdd-0.1.0.dist-info/licenses/LICENSE +674 -0
  183. atdd-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,168 @@
1
+ """
2
+ SPEC-CODER-CONV-0019: GREEN convention enforces cross-stack layer naming consistency
3
+
4
+ Test that green.convention.yaml enforces same layer names across all stacks.
5
+ """
6
+
7
+ import pytest
8
+ from pathlib import Path
9
+ import yaml
10
+
11
+
12
+ REPO_ROOT = Path(__file__).resolve().parents[4]
13
+ BACKEND_CONVENTION = REPO_ROOT / "atdd" / "coder" / "conventions" / "backend.convention.yaml"
14
+ FRONTEND_CONVENTION = REPO_ROOT / "atdd" / "coder" / "conventions" / "frontend.convention.yaml"
15
+
16
+
17
+ @pytest.mark.coder
18
+ def test_all_stacks_use_same_layer_names():
19
+ """
20
+ SPEC-CODER-CONV-0019: All stacks use same 4 layer names.
21
+
22
+ Given: Layer structure defined for Python, Flutter, and Supabase
23
+ When: Checking layer names across stacks
24
+ Then: All use presentation, application, domain, integration
25
+ """
26
+ assert BACKEND_CONVENTION.exists(), "backend.convention.yaml must exist"
27
+ assert FRONTEND_CONVENTION.exists(), "frontend.convention.yaml must exist"
28
+
29
+ with open(BACKEND_CONVENTION, 'r') as f:
30
+ backend_conv = yaml.safe_load(f)
31
+
32
+ with open(FRONTEND_CONVENTION, 'r') as f:
33
+ frontend_conv = yaml.safe_load(f)
34
+
35
+ expected_layers = {'presentation', 'application', 'domain', 'integration'}
36
+
37
+ # Check Python (backend)
38
+ backend = backend_conv['backend']
39
+ backend_structure = backend.get('structure', {})
40
+ if 'python' in backend_structure:
41
+ python_layers = set(backend_structure['python'].get('layers', []))
42
+ assert python_layers == expected_layers, \
43
+ f"Python layers {python_layers} must match {expected_layers}"
44
+
45
+ # Check Flutter/Dart (frontend)
46
+ frontend = frontend_conv['frontend']
47
+ frontend_structure = frontend.get('structure', {})
48
+ if 'flutter' in frontend_structure or 'dart' in frontend_structure:
49
+ flutter_layers = set(frontend_structure.get('flutter', frontend_structure.get('dart', {})).get('layers', []))
50
+ assert flutter_layers == expected_layers, \
51
+ f"Flutter layers {flutter_layers} must match {expected_layers}"
52
+
53
+ # Check Supabase/TypeScript (backend)
54
+ if 'supabase' in backend_structure or 'typescript' in backend_structure:
55
+ supabase_layers = set(backend_structure.get('supabase', backend_structure.get('typescript', {})).get('layers', []))
56
+ assert supabase_layers == expected_layers, \
57
+ f"Supabase layers {supabase_layers} must match {expected_layers}"
58
+
59
+
60
+ @pytest.mark.coder
61
+ def test_no_alternative_layer_names():
62
+ """
63
+ SPEC-CODER-CONV-0019: No alternative naming (data, infrastructure) allowed.
64
+
65
+ Given: Layer structure configuration
66
+ When: Checking for alternative layer names
67
+ Then: Only presentation, application, domain, integration are allowed
68
+ """
69
+ assert BACKEND_CONVENTION.exists(), "backend.convention.yaml must exist"
70
+ assert FRONTEND_CONVENTION.exists(), "frontend.convention.yaml must exist"
71
+
72
+ with open(BACKEND_CONVENTION, 'r') as f:
73
+ backend_conv = yaml.safe_load(f)
74
+
75
+ with open(FRONTEND_CONVENTION, 'r') as f:
76
+ frontend_conv = yaml.safe_load(f)
77
+
78
+ backend = backend_conv['backend']
79
+ backend_structure = backend.get('structure', {})
80
+
81
+ frontend = frontend_conv['frontend']
82
+ frontend_structure = frontend.get('structure', {})
83
+
84
+ # Check for forbidden alternatives (could be at convention level or structure level)
85
+ naming_rules = backend.get('naming_rules') or backend_structure.get('naming_rules', {})
86
+
87
+ if naming_rules:
88
+ forbidden_names = naming_rules.get('forbidden_layer_names') or \
89
+ naming_rules.get('disallowed_alternatives', [])
90
+
91
+ # Should forbid common alternatives
92
+ alternatives = ['data', 'infrastructure', 'adapters', 'interfaces']
93
+ for alt in alternatives:
94
+ if forbidden_names:
95
+ # At least some alternatives should be explicitly forbidden
96
+ pass # Convention should document this
97
+
98
+ # Alternative: check that only standard names are used
99
+ standard_layers = {'presentation', 'application', 'domain', 'integration'}
100
+
101
+ # Check backend stacks
102
+ for stack_name, stack_config in backend_structure.items():
103
+ if isinstance(stack_config, dict) and 'layers' in stack_config:
104
+ layers = set(stack_config['layers'])
105
+ non_standard = layers - standard_layers
106
+
107
+ assert len(non_standard) == 0, \
108
+ f"Backend stack {stack_name} has non-standard layers: {non_standard}"
109
+
110
+ # Check frontend stacks
111
+ for stack_name, stack_config in frontend_structure.items():
112
+ if isinstance(stack_config, dict) and 'layers' in stack_config:
113
+ layers = set(stack_config['layers'])
114
+ non_standard = layers - standard_layers
115
+
116
+ assert len(non_standard) == 0, \
117
+ f"Frontend stack {stack_name} has non-standard layers: {non_standard}"
118
+
119
+
120
+ @pytest.mark.coder
121
+ def test_layer_names_lowercase():
122
+ """
123
+ SPEC-CODER-CONV-0019: All layer directory names are lowercase.
124
+
125
+ Given: Layer structure configuration
126
+ When: Checking layer name casing
127
+ Then: All layer names are lowercase
128
+ """
129
+ assert BACKEND_CONVENTION.exists(), "backend.convention.yaml must exist"
130
+ assert FRONTEND_CONVENTION.exists(), "frontend.convention.yaml must exist"
131
+
132
+ with open(BACKEND_CONVENTION, 'r') as f:
133
+ backend_conv = yaml.safe_load(f)
134
+
135
+ with open(FRONTEND_CONVENTION, 'r') as f:
136
+ frontend_conv = yaml.safe_load(f)
137
+
138
+ backend = backend_conv['backend']
139
+ backend_structure = backend.get('structure', {})
140
+
141
+ frontend = frontend_conv['frontend']
142
+ frontend_structure = frontend.get('structure', {})
143
+
144
+ # Check all backend stacks
145
+ for stack_name, stack_config in backend_structure.items():
146
+ if isinstance(stack_config, dict) and 'layers' in stack_config:
147
+ layers = stack_config['layers']
148
+
149
+ for layer in layers:
150
+ assert layer == layer.lower(), \
151
+ f"Layer name '{layer}' in backend {stack_name} must be lowercase"
152
+
153
+ # Also check that it doesn't have special characters
154
+ assert layer.replace('_', '').isalnum(), \
155
+ f"Layer name '{layer}' should be alphanumeric (underscores allowed)"
156
+
157
+ # Check all frontend stacks
158
+ for stack_name, stack_config in frontend_structure.items():
159
+ if isinstance(stack_config, dict) and 'layers' in stack_config:
160
+ layers = stack_config['layers']
161
+
162
+ for layer in layers:
163
+ assert layer == layer.lower(), \
164
+ f"Layer name '{layer}' in {stack_name} must be lowercase"
165
+
166
+ # Also check that it doesn't have special characters
167
+ assert layer.replace('_', '').isalnum(), \
168
+ f"Layer name '{layer}' should be alphanumeric (underscores allowed)"
@@ -0,0 +1,148 @@
1
+ """
2
+ SPEC-CODER-CONV-0018: GREEN convention validates layer dependency rules
3
+
4
+ Test that green.convention.yaml enforces clean architecture dependency rules.
5
+ """
6
+
7
+ import pytest
8
+ from pathlib import Path
9
+ import yaml
10
+
11
+
12
+ REPO_ROOT = Path(__file__).resolve().parents[4]
13
+ BACKEND_CONVENTION = REPO_ROOT / "atdd" / "coder" / "conventions" / "backend.convention.yaml"
14
+ FRONTEND_CONVENTION = REPO_ROOT / "atdd" / "coder" / "conventions" / "frontend.convention.yaml"
15
+
16
+
17
+ @pytest.mark.coder
18
+ def test_domain_has_no_layer_imports():
19
+ """
20
+ SPEC-CODER-CONV-0018: Domain layer must be pure (no dependencies).
21
+
22
+ Given: Clean architecture dependency rules
23
+ When: Checking domain layer import rules
24
+ Then: Domain cannot import from application/presentation/integration
25
+ """
26
+ assert BACKEND_CONVENTION.exists(), "backend.convention.yaml must exist"
27
+
28
+ with open(BACKEND_CONVENTION, 'r') as f:
29
+ convention = yaml.safe_load(f)
30
+
31
+ # Backend has dependency rules at backend.dependency
32
+ backend = convention['backend']
33
+ assert 'dependency' in backend, "Backend must define dependency rules"
34
+
35
+ dep_rules = backend['dependency']
36
+
37
+ assert dep_rules is not None, "Dependency rules must be defined"
38
+
39
+ # Backend uses edge-based dependencies, not per-layer rules
40
+ # Check that domain is not listed as 'from' in any allowed_edges
41
+ allowed_edges = dep_rules.get('allowed_edges', [])
42
+ forbidden_examples = dep_rules.get('forbidden_examples', [])
43
+
44
+ # Domain should not be in 'from' field of any allowed edge
45
+ domain_edges = [edge for edge in allowed_edges if edge.get('from') == 'domain']
46
+ assert len(domain_edges) == 0, "Domain should not have outgoing dependencies"
47
+
48
+ # Verify forbidden examples include domain restrictions
49
+ assert any('domain →' in example or 'domain ->' in example for example in forbidden_examples), \
50
+ "Forbidden examples must include domain restrictions"
51
+
52
+
53
+ @pytest.mark.coder
54
+ def test_application_only_imports_domain():
55
+ """
56
+ SPEC-CODER-CONV-0018: Application can only depend on domain.
57
+
58
+ Given: Clean architecture dependency rules
59
+ When: Checking application layer import rules
60
+ Then: Application cannot import from presentation/integration
61
+ """
62
+ assert BACKEND_CONVENTION.exists(), "green.convention.yaml must exist"
63
+
64
+ with open(BACKEND_CONVENTION, 'r') as f:
65
+ convention = yaml.safe_load(f)
66
+
67
+ backend = convention["backend"]
68
+ dep_rules = backend.get('dependency')
69
+
70
+ allowed_edges = dep_rules.get('allowed_edges', [])
71
+ forbidden_examples = dep_rules.get('forbidden_examples', [])
72
+
73
+ # Find application edges
74
+ app_edges = [edge for edge in allowed_edges if edge.get('from') == 'application']
75
+
76
+ # Application should only depend on domain
77
+ assert len(app_edges) > 0, "Application must have defined dependencies"
78
+ app_edge = app_edges[0]
79
+ assert app_edge.get('to') == ['domain'], \
80
+ f"Application should only depend on domain, got {app_edge.get('to')}"
81
+
82
+ # Check forbidden examples
83
+ assert any('application → presentation' in example or 'application -> presentation' in example
84
+ for example in forbidden_examples), \
85
+ "Application to presentation must be forbidden"
86
+
87
+
88
+ @pytest.mark.coder
89
+ def test_integration_only_imports_domain():
90
+ """
91
+ SPEC-CODER-CONV-0018: Integration can only depend on domain.
92
+
93
+ Given: Clean architecture dependency rules
94
+ When: Checking integration layer import rules
95
+ Then: Integration cannot import from application/presentation
96
+ """
97
+ assert BACKEND_CONVENTION.exists(), "green.convention.yaml must exist"
98
+
99
+ with open(BACKEND_CONVENTION, 'r') as f:
100
+ convention = yaml.safe_load(f)
101
+
102
+ backend = convention["backend"]
103
+ dep_rules = backend.get('dependency')
104
+
105
+ allowed_edges = dep_rules.get('allowed_edges', [])
106
+
107
+ # Find integration edges
108
+ int_edges = [edge for edge in allowed_edges if edge.get('from') == 'integration']
109
+
110
+ # Integration should depend on application and domain
111
+ assert len(int_edges) > 0, "Integration must have defined dependencies"
112
+ int_edge = int_edges[0]
113
+ int_deps = int_edge.get('to', [])
114
+
115
+ # Integration typically depends on both application and domain in clean arch
116
+ assert 'domain' in int_deps, "Integration should depend on domain"
117
+ assert 'presentation' not in int_deps, "Integration should not depend on presentation"
118
+
119
+
120
+ @pytest.mark.coder
121
+ def test_presentation_imports_valid():
122
+ """
123
+ SPEC-CODER-CONV-0018: Presentation can depend on application and domain.
124
+
125
+ Given: Clean architecture dependency rules
126
+ When: Checking presentation layer import rules
127
+ Then: Presentation can import from application and domain
128
+ """
129
+ assert BACKEND_CONVENTION.exists(), "green.convention.yaml must exist"
130
+
131
+ with open(BACKEND_CONVENTION, 'r') as f:
132
+ convention = yaml.safe_load(f)
133
+
134
+ backend = convention["backend"]
135
+ dep_rules = backend.get('dependency')
136
+
137
+ allowed_edges = dep_rules.get('allowed_edges', [])
138
+
139
+ # Find presentation edges
140
+ pres_edges = [edge for edge in allowed_edges if edge.get('from') == 'presentation']
141
+
142
+ # Presentation can depend on application and domain
143
+ assert len(pres_edges) > 0, "Presentation must have defined dependencies"
144
+ pres_edge = pres_edges[0]
145
+ pres_deps = pres_edge.get('to', [])
146
+
147
+ assert 'application' in pres_deps, "Presentation must be allowed to depend on application"
148
+ assert 'domain' in pres_deps, "Presentation must be allowed to depend on domain"
@@ -0,0 +1,103 @@
1
+ """
2
+ SPEC-CODER-CONV-0015: GREEN convention defines 4-layer structure for Python
3
+
4
+ Test that backend.convention.yaml enforces 4-layer structure for Python implementation.
5
+ """
6
+
7
+ import pytest
8
+ from pathlib import Path
9
+ import yaml
10
+
11
+
12
+ REPO_ROOT = Path(__file__).resolve().parents[4]
13
+ BACKEND_CONVENTION = REPO_ROOT / "atdd" / "coder" / "conventions" / "backend.convention.yaml"
14
+
15
+
16
+ @pytest.mark.coder
17
+ def test_green_enforces_python_layers():
18
+ """
19
+ SPEC-CODER-CONV-0015: Backend convention enforces 4-layer structure for Python.
20
+
21
+ Given: backend.convention.yaml exists
22
+ When: Reading Python layer structure configuration
23
+ Then: Convention enforces 4 layer directories under src/
24
+ """
25
+ assert BACKEND_CONVENTION.exists(), "backend.convention.yaml must exist"
26
+
27
+ with open(BACKEND_CONVENTION, 'r') as f:
28
+ convention = yaml.safe_load(f)
29
+
30
+ # Backend convention has backend.structure.python
31
+ assert 'backend' in convention, "Convention must have 'backend' section"
32
+ backend = convention['backend']
33
+
34
+ assert 'structure' in backend, "Backend must define structure"
35
+ structure = backend['structure']
36
+
37
+ # Check Python-specific configuration
38
+ assert 'python' in structure, "Convention must define Python layer structure"
39
+
40
+ python_config = structure['python']
41
+
42
+ # Verify 4 layers are defined
43
+ assert 'layers' in python_config, "Python config must define layers"
44
+ layers = python_config['layers']
45
+
46
+ expected_layers = {'presentation', 'application', 'domain', 'integration'}
47
+ assert set(layers) == expected_layers, \
48
+ f"Layers must be {expected_layers}, got {set(layers)}"
49
+
50
+ # Verify source path pattern
51
+ assert 'src_path_pattern' in python_config or 'source_pattern' in python_config, \
52
+ "Python config must define src_path_pattern"
53
+
54
+ pattern = python_config.get('src_path_pattern') or python_config.get('source_pattern')
55
+
56
+ assert '{wagon}' in pattern, "Pattern must include {wagon} placeholder"
57
+ assert '{feature}' in pattern, "Pattern must include {feature} placeholder"
58
+ assert '{layer}' in pattern, "Pattern must include {layer} placeholder"
59
+ assert 'src' in pattern, "Pattern must include 'src' directory"
60
+
61
+ # Verify it matches expected pattern
62
+ expected_pattern = "python/{wagon}/{feature}/src/{layer}/"
63
+ assert pattern == expected_pattern or pattern.startswith("python/{wagon}/{feature}/src/{layer}"), \
64
+ f"Pattern should be {expected_pattern}, got {pattern}"
65
+
66
+
67
+ @pytest.mark.coder
68
+ def test_python_component_layer_mapping():
69
+ """
70
+ SPEC-CODER-CONV-0015: Component naming rules map to layers.
71
+
72
+ Given: Python layer structure with component types
73
+ When: Checking component-to-layer mapping
74
+ Then: Services in domain, repositories in integration, etc.
75
+ """
76
+ assert BACKEND_CONVENTION.exists(), "backend.convention.yaml must exist"
77
+
78
+ with open(BACKEND_CONVENTION, 'r') as f:
79
+ convention = yaml.safe_load(f)
80
+
81
+ backend = convention['backend']
82
+
83
+ # Backend has layers with component_types that define which components go where
84
+ layers_def = backend.get('layers', {})
85
+
86
+ # Check that component types are defined per layer
87
+ assert 'domain' in layers_def, "Domain layer must be defined"
88
+ assert 'integration' in layers_def, "Integration layer must be defined"
89
+ assert 'presentation' in layers_def, "Presentation layer must be defined"
90
+ assert 'application' in layers_def, "Application layer must be defined"
91
+
92
+ # Verify component types exist in appropriate layers
93
+ domain_types = [ct['name'] for ct in layers_def['domain'].get('component_types', [])]
94
+ assert 'services' in domain_types, "Services should be in domain layer"
95
+
96
+ integration_types = [ct['name'] for ct in layers_def['integration'].get('component_types', [])]
97
+ assert 'repositories' in integration_types, "Repositories should be in integration layer"
98
+
99
+ presentation_types = [ct['name'] for ct in layers_def['presentation'].get('component_types', [])]
100
+ assert 'controllers' in presentation_types, "Controllers should be in presentation layer"
101
+
102
+ application_types = [ct['name'] for ct in layers_def['application'].get('component_types', [])]
103
+ assert 'use_cases' in application_types, "Use cases should be in application layer"
@@ -0,0 +1,103 @@
1
+ """
2
+ SPEC-CODER-CONV-0017: GREEN convention defines 4-layer structure for Supabase
3
+
4
+ Test that green.convention.yaml enforces 4-layer structure for Supabase implementation.
5
+ """
6
+
7
+ import pytest
8
+ from pathlib import Path
9
+ import yaml
10
+
11
+
12
+ REPO_ROOT = Path(__file__).resolve().parents[4]
13
+ BACKEND_CONVENTION = REPO_ROOT / "atdd" / "coder" / "conventions" / "backend.convention.yaml"
14
+ FRONTEND_CONVENTION = REPO_ROOT / "atdd" / "coder" / "conventions" / "frontend.convention.yaml"
15
+
16
+
17
+ @pytest.mark.coder
18
+ def test_green_enforces_supabase_layers():
19
+ """
20
+ SPEC-CODER-CONV-0017: Backend convention enforces 4-layer structure for Supabase.
21
+
22
+ Given: Supabase implementation uses supabase/functions/{wagon}/{feature}/
23
+ When: Reading Supabase layer structure configuration
24
+ Then: Convention enforces 4 layer directories under function root
25
+ """
26
+ assert BACKEND_CONVENTION.exists(), "backend.convention.yaml must exist"
27
+
28
+ with open(BACKEND_CONVENTION, 'r') as f:
29
+ convention = yaml.safe_load(f)
30
+
31
+ backend = convention['backend']
32
+ assert 'structure' in backend, "Backend must define structure"
33
+ structure = backend['structure']
34
+
35
+ # Check Supabase/TypeScript-specific configuration
36
+ assert 'supabase' in structure or 'typescript' in structure, \
37
+ "Convention must define Supabase/TypeScript layer structure"
38
+
39
+ supabase_config = structure.get('supabase') or structure.get('typescript')
40
+
41
+ # Verify 4 layers are defined
42
+ assert 'layers' in supabase_config, "Supabase config must define layers"
43
+ layers = supabase_config['layers']
44
+
45
+ expected_layers = {'presentation', 'application', 'domain', 'integration'}
46
+ assert set(layers) == expected_layers, \
47
+ f"Layers must be {expected_layers}, got {set(layers)}"
48
+
49
+ # Verify source path pattern
50
+ assert 'src_path_pattern' in supabase_config or 'source_pattern' in supabase_config, \
51
+ "Supabase config must define src_path_pattern"
52
+
53
+ pattern = supabase_config.get('src_path_pattern') or supabase_config.get('source_pattern')
54
+
55
+ assert '{wagon}' in pattern, "Pattern must include {wagon} placeholder"
56
+ assert '{feature}' in pattern, "Pattern must include {feature} placeholder"
57
+ assert '{layer}' in pattern, "Pattern must include {layer} placeholder"
58
+ assert 'supabase/functions' in pattern or 'functions' in pattern, \
59
+ "Pattern must reference Supabase functions directory"
60
+
61
+ # Verify it matches expected pattern
62
+ expected_pattern = "supabase/functions/{wagon}/{feature}/{layer}/"
63
+ assert pattern == expected_pattern or \
64
+ pattern.startswith("supabase/functions/{wagon}/{feature}"), \
65
+ f"Pattern should reference {expected_pattern}, got {pattern}"
66
+
67
+
68
+ @pytest.mark.coder
69
+ def test_supabase_index_is_thin():
70
+ """
71
+ SPEC-CODER-CONV-0017: Validates index.ts has no business logic.
72
+
73
+ Given: Supabase layer structure with index.ts at root
74
+ When: Checking index.ts constraints
75
+ Then: index.ts contains only adapter code (imports presentation only)
76
+ """
77
+ assert BACKEND_CONVENTION.exists(), "backend.convention.yaml must exist"
78
+
79
+ with open(BACKEND_CONVENTION, 'r') as f:
80
+ convention = yaml.safe_load(f)
81
+
82
+ backend = convention['backend']
83
+ structure = backend.get('structure', {})
84
+ supabase_config = structure.get('supabase') or structure.get('typescript', {})
85
+
86
+ # Check for index.ts rules - might be under entrypoint_rules
87
+ index_rules = supabase_config.get('entrypoint_rules', {}).get('index_ts') or \
88
+ supabase_config.get('index_constraints')
89
+
90
+ if index_rules:
91
+ # index.ts should only import from presentation
92
+ assert 'allowed_imports' in index_rules or 'import_restrictions' in index_rules, \
93
+ "index.ts must have import restrictions"
94
+
95
+ allowed = index_rules.get('allowed_imports', [])
96
+ if allowed:
97
+ assert 'presentation' in allowed or './presentation' in str(allowed), \
98
+ "index.ts should only import from presentation layer"
99
+
100
+ # index.ts should not contain business logic
101
+ assert 'no_business_logic' in index_rules or \
102
+ index_rules.get('role') == 'thin_adapter', \
103
+ "index.ts must be documented as thin adapter with no business logic"