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,399 @@
1
+ """
2
+ SPEC-COACH-UTILS-0294: Generate complete contract metadata from wagon and feature interfaces
3
+ SPEC-COACH-UTILS-0295: Validate and update existing contract metadata completeness
4
+ SPEC-COACH-UTILS-0296: Create placeholder test files for scaffolded contracts
5
+
6
+ Tests the contract scaffold generation automation that reads wagon/feature interfaces
7
+ and auto-generates contract metadata following artifact-naming.convention.yaml.
8
+ """
9
+ import pytest
10
+ import yaml
11
+ import json
12
+ from pathlib import Path
13
+
14
+
15
+ @pytest.mark.platform
16
+ def test_scaffold_contract_metadata_from_wagon_and_feature_interfaces(tmp_path):
17
+ """
18
+ SPEC-COACH-UTILS-0294: Generate complete contract metadata from wagon and feature interfaces
19
+
20
+ Given: A contract artifact URN following artifact-naming.convention.yaml
21
+ Wagon manifests exist at plan/*/_*.yaml with produce[] and consume[] arrays
22
+ Feature manifests exist at plan/{wagon}/features/*.yaml with produces[] arrays
23
+ Artifact URN uses colon for hierarchy and dot for variant
24
+ Contract may or may not exist at derived file path from artifact URN
25
+ When: Scaffolding contract metadata from wagon and feature interfaces
26
+ Then: Parse artifact URN according to convention pattern theme(category)*aspect(.variant)?
27
+ Split URN by colons and dots to extract theme hierarchy aspect and variant
28
+ Convert artifact URN to contract file path using convention mapping
29
+ Scan all wagon manifests produce[] to find producer wagon
30
+ Cross-check producer wagon features[] produces[] arrays match wagon produce[]
31
+ Scan all wagon manifests consume[] to find consumer wagons
32
+ Extract dependencies from producer wagon consume[] array
33
+ Infer API method from aspect and variant patterns
34
+ Generate API path by joining theme hierarchy aspect with slashes
35
+ Extract traceability wagon_ref from producer wagon file path
36
+ Extract traceability feature_refs from producer wagon features[] URNs
37
+ Generate testing directory path and placeholder test file name
38
+ Create full contract scaffold with x-artifact-metadata
39
+ """
40
+ # Setup test directories
41
+ plan_dir = tmp_path / "plan"
42
+ plan_dir.mkdir()
43
+ contracts_dir = tmp_path / "contracts"
44
+ contracts_dir.mkdir()
45
+ convention_dir = tmp_path / ".claude" / "conventions" / "planner"
46
+ convention_dir.mkdir(parents=True)
47
+
48
+ # Create artifact naming convention file
49
+ convention_data = {
50
+ "version": "2.1",
51
+ "name": "Artifact Naming Convention",
52
+ "naming_pattern": {
53
+ "full_pattern": "{theme}(:{category})*:{aspect}(.{variant})?"
54
+ }
55
+ }
56
+ with open(convention_dir / "artifact-naming.convention.yaml", 'w') as f:
57
+ yaml.dump(convention_data, f)
58
+
59
+ # Create producer wagon that produces mechanic:timebank.exhausted
60
+ burn_timebank_dir = plan_dir / "burn_timebank"
61
+ burn_timebank_dir.mkdir()
62
+ wagon_manifest = burn_timebank_dir / "_burn_timebank.yaml"
63
+ wagon_data = {
64
+ "wagon": "burn-timebank",
65
+ "theme": "mechanic",
66
+ "produce": [
67
+ {"name": "mechanic:timebank.exhausted", "contract": "contract:mechanic:timebank.exhausted"}
68
+ ],
69
+ "consume": [
70
+ {"name": "contract:match:state.committed"}
71
+ ],
72
+ "features": [
73
+ {"name": "feature:burn-timebank:exhaust-timer"}
74
+ ]
75
+ }
76
+ with open(wagon_manifest, 'w') as f:
77
+ yaml.dump(wagon_data, f, default_flow_style=False, sort_keys=False)
78
+
79
+ # Create feature manifest that matches wagon produce
80
+ features_dir = burn_timebank_dir / "features"
81
+ features_dir.mkdir()
82
+ feature_manifest = features_dir / "exhaust_timer.yaml"
83
+ feature_data = {
84
+ "urn": "feature:burn-timebank:exhaust-timer",
85
+ "feature": "exhaust-timer",
86
+ "description": "Exhaust timebank timer",
87
+ "produces": [
88
+ {"name": "mechanic:timebank.exhausted", "contract": "contract:mechanic:timebank.exhausted"}
89
+ ]
90
+ }
91
+ with open(feature_manifest, 'w') as f:
92
+ yaml.dump(feature_data, f, default_flow_style=False, sort_keys=False)
93
+
94
+ # Create consumer wagon
95
+ reveal_status_dir = plan_dir / "reveal_status"
96
+ reveal_status_dir.mkdir()
97
+ consumer_manifest = reveal_status_dir / "_reveal_status.yaml"
98
+ consumer_data = {
99
+ "wagon": "reveal-status",
100
+ "theme": "sensory",
101
+ "consume": [
102
+ {"name": "contract:mechanic:timebank.exhausted"}
103
+ ]
104
+ }
105
+ with open(consumer_manifest, 'w') as f:
106
+ yaml.dump(consumer_data, f, default_flow_style=False, sort_keys=False)
107
+
108
+ # Import the scaffold function
109
+ from atdd.coach.commands.interface import scaffold_contract_metadata
110
+
111
+ # Execute scaffold generation
112
+ artifact_urn = "mechanic:timebank.exhausted"
113
+ result = scaffold_contract_metadata(
114
+ artifact_urn=artifact_urn,
115
+ plan_dir=plan_dir,
116
+ contracts_dir=contracts_dir,
117
+ convention_path=convention_dir / "artifact-naming.convention.yaml"
118
+ )
119
+
120
+ # Verify contract was created at correct path
121
+ expected_path = contracts_dir / "mechanic" / "timebank" / "exhausted.schema.json"
122
+ assert expected_path.exists(), f"Contract not created at {expected_path}"
123
+
124
+ # Read and verify contract content
125
+ with open(expected_path) as f:
126
+ contract = json.load(f)
127
+
128
+ # Verify x-artifact-metadata structure
129
+ metadata = contract.get("x-artifact-metadata", {})
130
+
131
+ # Verify domain and resource parsed from URN
132
+ assert metadata["domain"] == "timebank", "Domain should be 'timebank'"
133
+ assert metadata["resource"] == "timebank.exhausted", "Resource should be 'timebank.exhausted'"
134
+
135
+ # Verify version
136
+ assert metadata["version"] == "1.0.0", "Version should default to 1.0.0"
137
+
138
+ # Verify producer from wagon
139
+ assert metadata["producer"] == "wagon:burn-timebank", "Producer should be burn-timebank wagon"
140
+
141
+ # Verify consumers scanned from all wagons
142
+ assert "wagon:reveal-status" in metadata["consumers"], "Should find reveal-status as consumer"
143
+
144
+ # Verify dependencies from producer wagon consume[]
145
+ assert "contract:match:state.committed" in metadata["dependencies"], "Should extract dependencies"
146
+
147
+ # Verify API inferred from resource pattern (exhausted = event = POST)
148
+ api = metadata.get("api", {})
149
+ operations = api.get("operations", [])
150
+ assert len(operations) > 0, "Should have API operations"
151
+ assert operations[0]["method"] == "POST", "Exhausted event should be POST"
152
+ assert operations[0]["path"] == "/mechanic/timebank/exhausted", "Path should be /mechanic/timebank/exhausted"
153
+
154
+ # Verify traceability
155
+ traceability = metadata.get("traceability", {})
156
+ assert traceability["wagon_ref"] == "plan/burn_timebank/_burn_timebank.yaml", "Wagon ref should match"
157
+ assert "feature:burn-timebank:exhaust-timer" in traceability["feature_refs"], "Feature ref should match"
158
+
159
+ # Verify testing paths
160
+ testing = metadata.get("testing", {})
161
+ assert testing["directory"] == "contracts/mechanic/timebank/tests/", "Test directory should be correct"
162
+ assert "exhausted_schema_test.json" in testing["schema_tests"], "Test file should be exhausted_schema_test.json"
163
+
164
+ # Verify result summary
165
+ assert result["created"] == True, "Contract should be marked as created"
166
+ assert result["path"] == str(expected_path), "Result should contain path"
167
+
168
+
169
+ @pytest.mark.platform
170
+ def test_validate_and_update_existing_contract_metadata(tmp_path):
171
+ """
172
+ SPEC-COACH-UTILS-0295: Validate and update existing contract metadata completeness
173
+
174
+ Given: Contract schema exists with x-artifact-metadata section but may be incomplete
175
+ Wagon manifests have changed since contract was created
176
+ New consumers or dependencies may have been added
177
+ When: Validating and updating existing contract metadata
178
+ Then: Read existing contract x-artifact-metadata section
179
+ Re-scan wagon manifests to build complete metadata from current state
180
+ Compare existing metadata with generated metadata
181
+ Detect missing or outdated fields
182
+ Update only missing or outdated fields preserve user customizations
183
+ Do not overwrite manually edited fields
184
+ Validate updated contract against JSON Schema spec
185
+ Report what was updated and why
186
+ """
187
+ # Setup test directories
188
+ plan_dir = tmp_path / "plan"
189
+ plan_dir.mkdir()
190
+ contracts_dir = tmp_path / "contracts"
191
+ contracts_dir.mkdir()
192
+
193
+ # Create producer wagon
194
+ pace_dilemmas_dir = plan_dir / "pace_dilemmas"
195
+ pace_dilemmas_dir.mkdir()
196
+ wagon_manifest = pace_dilemmas_dir / "_pace_dilemmas.yaml"
197
+ wagon_data = {
198
+ "wagon": "pace-dilemmas",
199
+ "theme": "match",
200
+ "produce": [
201
+ {"name": "match:dilemma.current", "contract": "contract:match:dilemma.current"}
202
+ ]
203
+ }
204
+ with open(wagon_manifest, 'w') as f:
205
+ yaml.dump(wagon_data, f, default_flow_style=False, sort_keys=False)
206
+
207
+ # Create NEW consumer wagon (added after contract was created)
208
+ resolve_dilemmas_dir = plan_dir / "resolve_dilemmas"
209
+ resolve_dilemmas_dir.mkdir()
210
+ consumer_manifest = resolve_dilemmas_dir / "_resolve_dilemmas.yaml"
211
+ consumer_data = {
212
+ "wagon": "resolve-dilemmas",
213
+ "theme": "mechanic",
214
+ "consume": [
215
+ {"name": "contract:match:dilemma.current"}
216
+ ]
217
+ }
218
+ with open(consumer_manifest, 'w') as f:
219
+ yaml.dump(consumer_data, f, default_flow_style=False, sort_keys=False)
220
+
221
+ # Create EXISTING contract with INCOMPLETE metadata (missing new consumer)
222
+ dilemma_dir = contracts_dir / "match" / "dilemma"
223
+ dilemma_dir.mkdir(parents=True)
224
+ existing_contract = dilemma_dir / "current.schema.json"
225
+ contract_data = {
226
+ "$schema": "http://json-schema.org/draft-07/schema#",
227
+ "$id": "match:dilemma.current",
228
+ "version": "1.0.0",
229
+ "title": "Current Dilemma Contract",
230
+ "description": "CUSTOM DESCRIPTION - should not be overwritten",
231
+ "type": "object",
232
+ "properties": {
233
+ "id": {"type": "string"}
234
+ },
235
+ "x-artifact-metadata": {
236
+ "domain": "dilemma",
237
+ "resource": "dilemma.current",
238
+ "version": "1.0.0",
239
+ "producer": "wagon:pace-dilemmas",
240
+ "consumers": [], # INCOMPLETE - missing new consumer
241
+ "dependencies": [],
242
+ "api": {
243
+ "operations": [{
244
+ "method": "GET",
245
+ "path": "/match/dilemma/current",
246
+ "description": "CUSTOM API DESCRIPTION - should not be overwritten"
247
+ }]
248
+ },
249
+ "traceability": {
250
+ "wagon_ref": "plan/pace_dilemmas/_pace_dilemmas.yaml"
251
+ # Missing feature_refs
252
+ },
253
+ "testing": {
254
+ "directory": "contracts/match/dilemma/tests/"
255
+ # Missing schema_tests array
256
+ }
257
+ }
258
+ }
259
+ with open(existing_contract, 'w') as f:
260
+ json.dump(contract_data, f, indent=2)
261
+
262
+ # Import validation function
263
+ from atdd.coach.commands.interface import validate_and_update_contract_metadata
264
+
265
+ # Execute validation and update
266
+ result = validate_and_update_contract_metadata(
267
+ contract_path=existing_contract,
268
+ plan_dir=plan_dir,
269
+ contracts_dir=contracts_dir
270
+ )
271
+
272
+ # Read updated contract
273
+ with open(existing_contract) as f:
274
+ updated_contract = json.load(f)
275
+
276
+ metadata = updated_contract["x-artifact-metadata"]
277
+
278
+ # Verify new consumer was added
279
+ assert "wagon:resolve-dilemmas" in metadata["consumers"], "Should add new consumer"
280
+
281
+ # Verify custom description was preserved
282
+ assert updated_contract["description"] == "CUSTOM DESCRIPTION - should not be overwritten", \
283
+ "Should preserve custom description"
284
+
285
+ # Verify custom API description was preserved
286
+ api_desc = metadata["api"]["operations"][0].get("description", "")
287
+ assert "CUSTOM API DESCRIPTION" in api_desc, "Should preserve custom API description"
288
+
289
+ # Verify missing feature_refs was added
290
+ assert "feature_refs" in metadata["traceability"], "Should add missing feature_refs"
291
+
292
+ # Verify missing schema_tests was added
293
+ assert "schema_tests" in metadata["testing"], "Should add missing schema_tests array"
294
+ assert len(metadata["testing"]["schema_tests"]) > 0, "Should populate schema_tests"
295
+
296
+ # Verify result report
297
+ assert "consumers" in result["updates"], "Should report consumers update"
298
+ assert "traceability.feature_refs" in result["updates"], "Should report feature_refs added"
299
+ assert "testing.schema_tests" in result["updates"], "Should report schema_tests added"
300
+ assert result["preserved_customizations"] > 0, "Should count preserved customizations"
301
+
302
+
303
+ @pytest.mark.platform
304
+ def test_create_placeholder_contract_tests(tmp_path):
305
+ """
306
+ SPEC-COACH-UTILS-0296: Create placeholder test files for scaffolded contracts
307
+
308
+ Given: Contract has been scaffolded with x-artifact-metadata.testing section
309
+ Testing directory path specified in x-artifact-metadata.testing.directory
310
+ Test file names specified in x-artifact-metadata.testing.schema_tests array
311
+ Test directory may or may not exist
312
+ Test files may or may not exist
313
+ When: Creating placeholder test files for new contracts
314
+ Then: Create testing directory if it does not exist
315
+ For each test file in schema_tests array check if file exists
316
+ If test file does not exist create placeholder test JSON file
317
+ Placeholder contains minimal valid test structure with contract reference
318
+ Placeholder includes TODO comment indicating it needs implementation
319
+ Existing test files are not overwritten or modified
320
+ Report which test files were created vs already existed
321
+ """
322
+ # Setup test directories
323
+ contracts_dir = tmp_path / "contracts"
324
+ contracts_dir.mkdir()
325
+
326
+ # Create contract with testing metadata
327
+ mechanic_dir = contracts_dir / "mechanic" / "timebank"
328
+ mechanic_dir.mkdir(parents=True)
329
+ contract_path = mechanic_dir / "exhausted.schema.json"
330
+
331
+ contract_data = {
332
+ "$schema": "http://json-schema.org/draft-07/schema#",
333
+ "$id": "mechanic:timebank.exhausted",
334
+ "version": "1.0.0",
335
+ "title": "Timebank Exhausted",
336
+ "type": "object",
337
+ "x-artifact-metadata": {
338
+ "domain": "timebank",
339
+ "resource": "timebank.exhausted",
340
+ "testing": {
341
+ "directory": "contracts/mechanic/timebank/tests/",
342
+ "schema_tests": [
343
+ "exhausted_schema_test.json",
344
+ "exhausted_validation_test.json"
345
+ ]
346
+ }
347
+ }
348
+ }
349
+ with open(contract_path, 'w') as f:
350
+ json.dump(contract_data, f, indent=2)
351
+
352
+ # Create ONE existing test file to verify it's not overwritten
353
+ tests_dir = mechanic_dir / "tests"
354
+ tests_dir.mkdir()
355
+ existing_test = tests_dir / "exhausted_schema_test.json"
356
+ existing_test_data = {
357
+ "description": "EXISTING TEST - should not be overwritten",
358
+ "contract": "mechanic:timebank.exhausted"
359
+ }
360
+ with open(existing_test, 'w') as f:
361
+ json.dump(existing_test_data, f, indent=2)
362
+
363
+ # Import function
364
+ from atdd.coach.commands.interface import create_placeholder_test_files
365
+
366
+ # Execute placeholder generation
367
+ result = create_placeholder_test_files(
368
+ contract_path=contract_path,
369
+ contracts_dir=contracts_dir
370
+ )
371
+
372
+ # Verify test directory exists
373
+ assert tests_dir.exists(), "Test directory should exist"
374
+
375
+ # Verify existing test was NOT modified
376
+ with open(existing_test) as f:
377
+ preserved_test = json.load(f)
378
+ assert preserved_test["description"] == "EXISTING TEST - should not be overwritten", \
379
+ "Should not overwrite existing test"
380
+
381
+ # Verify new test was created
382
+ new_test = tests_dir / "exhausted_validation_test.json"
383
+ assert new_test.exists(), "Should create new placeholder test"
384
+
385
+ # Verify placeholder structure
386
+ with open(new_test) as f:
387
+ placeholder = json.load(f)
388
+
389
+ assert "TODO" in placeholder.get("description", ""), "Should include TODO comment"
390
+ assert placeholder.get("contract") == "mechanic:timebank.exhausted", "Should reference contract"
391
+ assert "test_cases" in placeholder, "Should have test_cases structure"
392
+
393
+ # Verify result report
394
+ assert result["created"] == 1, "Should report 1 file created"
395
+ assert result["skipped"] == 1, "Should report 1 file skipped (existing)"
396
+ assert "exhausted_validation_test.json" in result["created_files"], \
397
+ "Should list created file"
398
+ assert "exhausted_schema_test.json" in result["skipped_files"], \
399
+ "Should list skipped file"
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test runner for ATDD meta-tests.
4
+
5
+ Replaces run_all_tests.sh with a more flexible Python-based test runner.
6
+ """
7
+
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import List, Optional
12
+
13
+
14
+ class TestRunner:
15
+ """Run ATDD meta-tests with various configurations."""
16
+
17
+ def __init__(self, repo_root: Path = None):
18
+ self.repo_root = repo_root or Path(__file__).parent.parent.parent.parent
19
+ self.atdd_dir = self.repo_root / "atdd"
20
+
21
+ def run_tests(
22
+ self,
23
+ phase: Optional[str] = None,
24
+ verbose: bool = False,
25
+ coverage: bool = False,
26
+ html_report: bool = False,
27
+ markers: Optional[List[str]] = None,
28
+ parallel: bool = True,
29
+ ) -> int:
30
+ """
31
+ Run ATDD tests with specified options.
32
+
33
+ Args:
34
+ phase: Test phase to run (planner, tester, coder, all, None=all)
35
+ verbose: Enable verbose output
36
+ coverage: Generate coverage report
37
+ html_report: Generate HTML report
38
+ markers: Additional pytest markers to filter
39
+ parallel: Run tests in parallel (uses pytest-xdist)
40
+
41
+ Returns:
42
+ Exit code from pytest
43
+ """
44
+ # Build pytest command
45
+ cmd = ["pytest"]
46
+
47
+ # Determine test path
48
+ if phase and phase != "all":
49
+ test_path = self.atdd_dir / phase
50
+ if not test_path.exists():
51
+ print(f"โŒ Error: Test phase '{phase}' not found at {test_path}")
52
+ return 1
53
+ cmd.append(str(test_path))
54
+ else:
55
+ # Run all atdd tests
56
+ cmd.append(str(self.atdd_dir))
57
+
58
+ # Add verbosity
59
+ if verbose:
60
+ cmd.append("-v")
61
+ else:
62
+ cmd.append("-q")
63
+
64
+ # Add markers
65
+ if markers:
66
+ for marker in markers:
67
+ cmd.extend(["-m", marker])
68
+
69
+ # Add coverage
70
+ if coverage:
71
+ cmd.extend([
72
+ "--cov=atdd",
73
+ "--cov-report=term-missing",
74
+ "--cov-report=html:atdd/htmlcov"
75
+ ])
76
+
77
+ # Add HTML report
78
+ if html_report:
79
+ cmd.extend([
80
+ "--html=atdd/test_report.html",
81
+ "--self-contained-html"
82
+ ])
83
+
84
+ # Add parallel execution
85
+ if parallel:
86
+ cmd.extend(["-n", "auto"])
87
+
88
+ # Show collected tests summary
89
+ cmd.append("--tb=short")
90
+
91
+ # Run pytest
92
+ print(f"๐Ÿงช Running: {' '.join(cmd)}")
93
+ print("=" * 60)
94
+
95
+ result = subprocess.run(cmd, cwd=self.repo_root)
96
+ return result.returncode
97
+
98
+ def run_phase(self, phase: str, **kwargs) -> int:
99
+ """Run tests for a specific phase."""
100
+ return self.run_tests(phase=phase, **kwargs)
101
+
102
+ def run_all(self, **kwargs) -> int:
103
+ """Run all ATDD meta-tests."""
104
+ return self.run_tests(phase="all", **kwargs)
105
+
106
+ def quick_check(self) -> int:
107
+ """Quick smoke test - run without parallelization."""
108
+ print("๐Ÿš€ Running quick check (no parallel)...")
109
+ return self.run_tests(
110
+ phase="all",
111
+ verbose=False,
112
+ parallel=False,
113
+ html_report=False
114
+ )
115
+
116
+ def full_suite(self) -> int:
117
+ """Full test suite with coverage and HTML report."""
118
+ print("๐ŸŽฏ Running full test suite...")
119
+ return self.run_tests(
120
+ phase="all",
121
+ verbose=True,
122
+ coverage=True,
123
+ html_report=True,
124
+ parallel=True
125
+ )
126
+
127
+
128
+ def main():
129
+ """CLI entry point for test runner."""
130
+ runner = TestRunner()
131
+
132
+ # Simple usage for now - can be enhanced with argparse
133
+ if len(sys.argv) > 1:
134
+ phase = sys.argv[1]
135
+ return runner.run_phase(phase, verbose=True, html_report=True)
136
+ else:
137
+ return runner.run_all(verbose=True, html_report=True)
138
+
139
+
140
+ if __name__ == "__main__":
141
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ # Test module for coach commands