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,297 @@
1
+ """
2
+ SPEC-COACH-UTILS-0292: Detect consumer mismatches between wagon manifests and contract schemas
3
+ SPEC-COACH-UTILS-0293: Apply consumer synchronization updates with user approval
4
+
5
+ Tests validation and synchronization of consumer declarations between:
6
+ - Wagon manifests (plan/*/_*.yaml)
7
+ - Feature manifests (plan/*/*/*.yaml)
8
+ - Contract schemas (contracts/**/*.schema.json)
9
+ """
10
+ import pytest
11
+ import yaml
12
+ import json
13
+ from pathlib import Path
14
+
15
+
16
+ @pytest.mark.platform
17
+ def test_detect_consumer_mismatches(tmp_path):
18
+ """
19
+ SPEC-COACH-UTILS-0292: Detect consumer mismatches between wagon manifests and contract schemas
20
+
21
+ Given: Wagon manifests exist at plan/*/_*.yaml with optional consumers field
22
+ Feature manifests exist at plan/*/*/*.yaml with optional consumers field
23
+ Contract schemas exist at contracts/**/*.schema.json with x-artifact-metadata.consumers
24
+ Consumers in wagon manifests reference contracts as contract:domain:resource
25
+ Consumers in contract schemas follow pattern wagon:name or external:service
26
+ Some manifests may declare consumers that contracts don't list
27
+ Some contracts may list consumers that manifests don't declare
28
+ When: Running consumer validation between manifests and contracts
29
+ Then: All wagon manifests are scanned for consumer declarations
30
+ All feature manifests are scanned for consumer declarations
31
+ All contract schemas are scanned for x-artifact-metadata.consumers
32
+ Mismatches are detected in both directions
33
+ Direction 1 manifest→contract shows wagons/features declaring contracts not listing them as consumers
34
+ Direction 2 contract→manifest shows contracts listing wagon consumers not declared in manifest
35
+ Report shows three fix options 1-update manifests only 2-update contracts only 3-mutual sync both
36
+ No changes applied without user approval
37
+ """
38
+ # Setup test directories
39
+ plan_dir = tmp_path / "plan"
40
+ plan_dir.mkdir()
41
+ contracts_dir = tmp_path / "contracts"
42
+ contracts_dir.mkdir()
43
+
44
+ # Create wagon manifest that declares consuming a contract
45
+ wagon_dir = plan_dir / "test_wagon"
46
+ wagon_dir.mkdir()
47
+ wagon_manifest = wagon_dir / "_test_wagon.yaml"
48
+ wagon_data = {
49
+ "wagon": "test-wagon",
50
+ "description": "Test wagon",
51
+ "consume": [
52
+ {"name": "contract:match:dilemma.current"}
53
+ ]
54
+ }
55
+ with open(wagon_manifest, 'w') as f:
56
+ yaml.dump(wagon_data, f, default_flow_style=False, sort_keys=False)
57
+
58
+ # Create feature manifest that declares consuming a contract
59
+ features_dir = wagon_dir / "features"
60
+ features_dir.mkdir()
61
+ feature_manifest = features_dir / "choose_option.yaml"
62
+ feature_data = {
63
+ "feature": "choose-option",
64
+ "description": "Choose dilemma option",
65
+ "consume": [
66
+ {"name": "contract:match:dilemma.paired"}
67
+ ]
68
+ }
69
+ with open(feature_manifest, 'w') as f:
70
+ yaml.dump(feature_data, f, default_flow_style=False, sort_keys=False)
71
+
72
+ # Create contract that DOES list the wagon as consumer (should match)
73
+ dilemma_dir = contracts_dir / "dilemma"
74
+ dilemma_dir.mkdir()
75
+ current_contract = dilemma_dir / "current.schema.json"
76
+ current_data = {
77
+ "$schema": "http://json-schema.org/draft-07/schema#",
78
+ "$id": "match:dilemma.current",
79
+ "title": "CurrentDilemma",
80
+ "description": "Current dilemma contract",
81
+ "type": "object",
82
+ "x-artifact-metadata": {
83
+ "domain": "dilemma",
84
+ "resource": "current",
85
+ "api": {"method": "GET", "path": "/dilemmas/current"},
86
+ "producer": "wagon:pace-dilemmas",
87
+ "consumers": ["wagon:test-wagon"], # Matches wagon manifest
88
+ "dependencies": [],
89
+ "traceability": {
90
+ "wagon_ref": "plan/pace_dilemmas/_pace_dilemmas.yaml",
91
+ "feature_refs": ["feature:pace-dilemmas:select-dilemma"]
92
+ },
93
+ "testing": {
94
+ "directory": "contracts/dilemma/tests/",
95
+ "schema_tests": ["current_schema_test.json"]
96
+ }
97
+ }
98
+ }
99
+ with open(current_contract, 'w') as f:
100
+ json.dump(current_data, f, indent=2)
101
+
102
+ # Create contract that DOES NOT list the feature as consumer (mismatch)
103
+ paired_contract = dilemma_dir / "paired.schema.json"
104
+ paired_data = {
105
+ "$schema": "http://json-schema.org/draft-07/schema#",
106
+ "$id": "match:dilemma.paired",
107
+ "title": "PairedDilemma",
108
+ "description": "Paired dilemma contract",
109
+ "type": "object",
110
+ "x-artifact-metadata": {
111
+ "domain": "dilemma",
112
+ "resource": "paired",
113
+ "api": {"method": "GET", "path": "/dilemmas/paired"},
114
+ "producer": "wagon:pace-dilemmas",
115
+ "consumers": [], # MISSING wagon:test-wagon - this is a mismatch!
116
+ "dependencies": [],
117
+ "traceability": {
118
+ "wagon_ref": "plan/pace_dilemmas/_pace_dilemmas.yaml",
119
+ "feature_refs": ["feature:pace-dilemmas:pair-fragments"]
120
+ },
121
+ "testing": {
122
+ "directory": "contracts/dilemma/tests/",
123
+ "schema_tests": ["paired_schema_test.json"]
124
+ }
125
+ }
126
+ }
127
+ with open(paired_contract, 'w') as f:
128
+ json.dump(paired_data, f, indent=2)
129
+
130
+ # Create contract that lists a consumer not declared in any manifest (reverse mismatch)
131
+ ux_dir = contracts_dir / "ux"
132
+ ux_dir.mkdir()
133
+ foundations_dir = ux_dir / "foundations"
134
+ foundations_dir.mkdir()
135
+ color_contract = foundations_dir / "color.schema.json"
136
+ color_data = {
137
+ "$schema": "http://json-schema.org/draft-07/schema#",
138
+ "$id": "ux:foundations",
139
+ "title": "ColorFoundations",
140
+ "description": "Color foundations contract",
141
+ "type": "object",
142
+ "x-artifact-metadata": {
143
+ "domain": "ux",
144
+ "resource": "foundations",
145
+ "collection": True,
146
+ "member": "color",
147
+ "api": {"method": "GET", "path": "/ux/foundations/color"},
148
+ "producer": "wagon:maintain-ux",
149
+ "consumers": ["wagon:nonexistent-wagon"], # Not declared in any manifest!
150
+ "dependencies": [],
151
+ "traceability": {
152
+ "wagon_ref": "plan/maintain_ux/_maintain_ux.yaml",
153
+ "feature_refs": ["feature:maintain-ux:design-tokens"]
154
+ },
155
+ "testing": {
156
+ "directory": "contracts/commons/ux/foundations/tests/",
157
+ "schema_tests": ["color_schema_test.json"]
158
+ }
159
+ }
160
+ }
161
+ with open(color_contract, 'w') as f:
162
+ json.dump(color_data, f, indent=2)
163
+
164
+ # Import the validator (will be implemented)
165
+ from atdd.coach.commands.consumers import ConsumerValidator
166
+
167
+ # Run validation
168
+ validator = ConsumerValidator(tmp_path)
169
+ report = validator.detect_mismatches()
170
+
171
+ # Assertions - check that mismatches were detected
172
+ assert "manifest_to_contract" in report, "Should detect manifest→contract mismatches"
173
+ assert "contract_to_manifest" in report, "Should detect contract→manifest mismatches"
174
+
175
+ # Check manifest→contract mismatch (feature declares contract:match:dilemma.paired but contract doesn't list it)
176
+ manifest_to_contract = report["manifest_to_contract"]
177
+ assert len(manifest_to_contract) == 1, "Should find 1 manifest→contract mismatch"
178
+ mismatch = manifest_to_contract[0]
179
+ assert mismatch["manifest"] == str(feature_manifest.relative_to(tmp_path))
180
+ assert mismatch["contract"] == "contract:match:dilemma.paired"
181
+ assert "dilemma/paired.schema.json" in mismatch["contract_file"]
182
+
183
+ # Check contract→manifest mismatch (contract lists wagon:nonexistent-wagon but no manifest declares it)
184
+ contract_to_manifest = report["contract_to_manifest"]
185
+ assert len(contract_to_manifest) == 1, "Should find 1 contract→manifest mismatch"
186
+ mismatch = contract_to_manifest[0]
187
+ assert "ux/foundations/color.schema.json" in mismatch["contract_file"]
188
+ assert mismatch["consumer"] == "wagon:nonexistent-wagon"
189
+
190
+ # Verify no changes were made (validation only)
191
+ with open(paired_contract, 'r') as f:
192
+ unchanged_contract = json.load(f)
193
+ assert unchanged_contract["x-artifact-metadata"]["consumers"] == [], "Contract should be unchanged"
194
+
195
+
196
+ @pytest.mark.platform
197
+ def test_apply_consumer_sync_updates(tmp_path):
198
+ """
199
+ SPEC-COACH-UTILS-0293: Apply consumer synchronization updates with user approval
200
+
201
+ Given: Consumer mismatches detected between manifests and contracts
202
+ User has selected fix direction 1-manifests 2-contracts 3-mutual
203
+ Target files exist and are valid YAML/JSON
204
+ When: Applying consumer synchronization updates
205
+ Then: If option 1 adds missing consumer references to wagon manifests only
206
+ If option 2 adds missing consumers to contract x-artifact-metadata.consumers only
207
+ If option 3 syncs both directions adding to manifests and contracts
208
+ Wagon manifest updates add contract references in format contract:domain:resource
209
+ Contract schema updates add consumers in format wagon:name or external:service
210
+ All updates preserve existing consumers no duplicates added
211
+ Updated files validate against respective schemas
212
+ YAML and JSON formatting preserved
213
+ Summary report shows all applied changes
214
+ """
215
+ # Setup test directories
216
+ plan_dir = tmp_path / "plan"
217
+ plan_dir.mkdir()
218
+ contracts_dir = tmp_path / "contracts"
219
+ contracts_dir.mkdir()
220
+
221
+ # Create feature manifest missing consumer declaration
222
+ wagon_dir = plan_dir / "test_wagon"
223
+ wagon_dir.mkdir()
224
+ features_dir = wagon_dir / "features"
225
+ features_dir.mkdir()
226
+ feature_manifest = features_dir / "choose_option.yaml"
227
+ feature_data = {
228
+ "feature": "choose-option",
229
+ "description": "Choose dilemma option",
230
+ "consume": [] # Empty - will be updated
231
+ }
232
+ with open(feature_manifest, 'w') as f:
233
+ yaml.dump(feature_data, f, default_flow_style=False, sort_keys=False)
234
+
235
+ # Create contract missing consumer in metadata
236
+ dilemma_dir = contracts_dir / "dilemma"
237
+ dilemma_dir.mkdir()
238
+ current_contract = dilemma_dir / "current.schema.json"
239
+ current_data = {
240
+ "$schema": "http://json-schema.org/draft-07/schema#",
241
+ "$id": "match:dilemma.current",
242
+ "title": "CurrentDilemma",
243
+ "description": "Current dilemma contract",
244
+ "type": "object",
245
+ "x-artifact-metadata": {
246
+ "domain": "dilemma",
247
+ "resource": "current",
248
+ "api": {"method": "GET", "path": "/dilemmas/current"},
249
+ "producer": "wagon:pace-dilemmas",
250
+ "consumers": [], # Empty - will be updated
251
+ "dependencies": [],
252
+ "traceability": {
253
+ "wagon_ref": "plan/pace_dilemmas/_pace_dilemmas.yaml",
254
+ "feature_refs": ["feature:pace-dilemmas:select-dilemma"]
255
+ },
256
+ "testing": {
257
+ "directory": "contracts/dilemma/tests/",
258
+ "schema_tests": ["current_schema_test.json"]
259
+ }
260
+ }
261
+ }
262
+ with open(current_contract, 'w') as f:
263
+ json.dump(current_data, f, indent=2)
264
+
265
+ # Import the validator
266
+ from atdd.coach.commands.consumers import ConsumerValidator
267
+
268
+ validator = ConsumerValidator(tmp_path)
269
+
270
+ # Test option 3: Mutual sync (both directions)
271
+ updates = [
272
+ {
273
+ "type": "manifest_to_contract",
274
+ "manifest_file": str(feature_manifest),
275
+ "contract_file": str(current_contract),
276
+ "contract_ref": "contract:match:dilemma.current",
277
+ "consumer_ref": "wagon:test-wagon"
278
+ }
279
+ ]
280
+
281
+ summary = validator.apply_updates(updates, direction="mutual")
282
+
283
+ # Verify manifest was updated
284
+ with open(feature_manifest, 'r') as f:
285
+ updated_feature = yaml.safe_load(f)
286
+ assert len(updated_feature["consume"]) == 1, "Feature should have 1 consumer"
287
+ assert updated_feature["consume"][0]["name"] == "contract:match:dilemma.current"
288
+
289
+ # Verify contract was updated
290
+ with open(current_contract, 'r') as f:
291
+ updated_contract = json.load(f)
292
+ assert len(updated_contract["x-artifact-metadata"]["consumers"]) == 1
293
+ assert "wagon:test-wagon" in updated_contract["x-artifact-metadata"]["consumers"]
294
+
295
+ # Verify summary report
296
+ assert "applied" in summary
297
+ assert summary["applied"] == 2 # Both manifest and contract updated
atdd/coder/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Coder audits, conventions and schemas."""
@@ -0,0 +1,88 @@
1
+ recipe: adapter
2
+ pattern: "Adapter (implements port interface)"
3
+ category: integration
4
+ source: "GoF Design Patterns + Hexagonal Architecture"
5
+ utils: "architecture.py"
6
+
7
+ applies_when:
8
+ - "Port interface exists without implementation"
9
+ - "Need external system integration"
10
+
11
+ steps:
12
+ - step: 1
13
+ what: "Verify port interface exists in application layer"
14
+ where: "application/ports/{resource}_repository.py"
15
+ example: |
16
+ class OrderRepository:
17
+ """Port for order persistence."""
18
+
19
+ def save(self, order: Order) -> None:
20
+ raise NotImplementedError
21
+
22
+ def find_by_id(self, id: OrderId) -> Order:
23
+ raise NotImplementedError
24
+
25
+ - step: 2
26
+ what: "Implement adapter in integration layer"
27
+ where: "integration/{tech}_{resource}_repository.py"
28
+ naming: "{Technology}{ResourceName}Repository"
29
+ template: |
30
+ class PostgresOrderRepository(OrderRepository):
31
+ """PostgreSQL adapter for OrderRepository port."""
32
+
33
+ def __init__(self, db: Database):
34
+ self.db = db
35
+
36
+ def save(self, order: Order) -> None:
37
+ row = OrderMapper.to_row(order)
38
+ self.db.insert('orders', row)
39
+
40
+ def find_by_id(self, id: OrderId) -> Order:
41
+ row = self.db.find_one('orders', {'id': id.value})
42
+ if not row:
43
+ raise OrderNotFound(id)
44
+ return OrderMapper.to_domain(row)
45
+
46
+ verify:
47
+ - run: pytest -xvs tests/integration/test_postgres_order_repository.py
48
+ expect: GREEN
49
+
50
+ - step: 3
51
+ what: "Create mapper (isolate domain from infrastructure)"
52
+ where: "integration/mappers/{resource}_mapper.py"
53
+ purpose: "Prevent infrastructure types from leaking into domain"
54
+ template: |
55
+ class OrderMapper:
56
+ """Map between Order domain entity and database row."""
57
+
58
+ @staticmethod
59
+ def to_row(order: Order) -> dict:
60
+ return {
61
+ "id": order.id.value,
62
+ "customer_id": order.customer_id.value,
63
+ "total_cents": order.total.cents,
64
+ "status": order.status.value,
65
+ "created_at": order.created_at.isoformat()
66
+ }
67
+
68
+ @staticmethod
69
+ def to_domain(row: dict) -> Order:
70
+ return Order.reconstitute(
71
+ id=OrderId(row["id"]),
72
+ customer_id=CustomerId(row["customer_id"]),
73
+ total=Money.from_cents(row["total_cents"]),
74
+ status=OrderStatus(row["status"]),
75
+ created_at=datetime.fromisoformat(row["created_at"])
76
+ )
77
+
78
+ pattern_distinction:
79
+ adapter: "IMPLEMENTS the port interface"
80
+ strategy: "SELECTS which adapter to use at runtime"
81
+ mapper: "TRANSFORMS between domain and infrastructure types"
82
+
83
+ final_verify:
84
+ - "Adapter implements port interface"
85
+ - "Domain layer has NO infrastructure imports"
86
+ - "Mapper isolates type transformations"
87
+ - "Tests can swap implementations (in-memory vs real)"
88
+ - "All tests GREEN"