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,575 @@
1
+ """
2
+ Test unified registry system.
3
+
4
+ Specs: SPEC-COACH-UTILS-0200 through SPEC-COACH-UTILS-0214
5
+ Location: .claude/agents/coach/utils.spec.yaml::unified_registry
6
+ """
7
+ import pytest
8
+ from pathlib import Path
9
+ from unittest.mock import Mock, patch, MagicMock
10
+ import yaml
11
+ import json
12
+ import tempfile
13
+ import shutil
14
+
15
+
16
+ # Test fixtures
17
+ @pytest.fixture
18
+ def temp_repo():
19
+ """Create temporary repository structure for testing."""
20
+ temp_dir = tempfile.mkdtemp()
21
+ repo_root = Path(temp_dir)
22
+
23
+ # Create directory structure
24
+ (repo_root / "plan").mkdir()
25
+ (repo_root / "contracts").mkdir()
26
+ (repo_root / "telemetry").mkdir()
27
+ (repo_root / "atdd" / "tester").mkdir(parents=True)
28
+ (repo_root / "python").mkdir()
29
+ (repo_root / "supabase" / "functions").mkdir(parents=True)
30
+
31
+ yield repo_root
32
+
33
+ # Cleanup
34
+ shutil.rmtree(temp_dir)
35
+
36
+
37
+ @pytest.fixture
38
+ def sample_wagon_manifest(temp_repo):
39
+ """Create sample wagon manifest."""
40
+ wagon_dir = temp_repo / "plan" / "test-wagon"
41
+ wagon_dir.mkdir()
42
+
43
+ manifest = {
44
+ "wagon": "test-wagon",
45
+ "description": "Test wagon for registry",
46
+ "theme": "testing",
47
+ "produce": [],
48
+ "consume": []
49
+ }
50
+
51
+ manifest_path = wagon_dir / "_test-wagon.yaml"
52
+ with open(manifest_path, "w") as f:
53
+ yaml.dump(manifest, f)
54
+
55
+ return manifest_path
56
+
57
+
58
+ @pytest.fixture
59
+ def sample_wagons_registry(temp_repo):
60
+ """Create sample plan/_wagons.yaml registry."""
61
+ registry_data = {
62
+ "wagons": [
63
+ {
64
+ "wagon": "existing-wagon",
65
+ "description": "Existing wagon",
66
+ "path": "plan/existing-wagon/",
67
+ "manifest": "plan/existing-wagon/_existing-wagon.yaml"
68
+ },
69
+ {
70
+ "wagon": "draft-wagon",
71
+ "description": "Draft wagon without manifest"
72
+ # No path/manifest = draft mode
73
+ }
74
+ ]
75
+ }
76
+
77
+ registry_path = temp_repo / "plan" / "_wagons.yaml"
78
+ with open(registry_path, "w") as f:
79
+ yaml.dump(registry_data, f)
80
+
81
+ return registry_path
82
+
83
+
84
+ # SPEC-COACH-UTILS-0200
85
+ def test_load_all_registries(temp_repo, sample_wagons_registry):
86
+ """
87
+ Test that registry command loads all registries without distinction.
88
+
89
+ Given: User runs registry (no flags)
90
+ When: Registry loader executes
91
+ Then: Loads all registry manifests, returns unified view, read-only mode
92
+ """
93
+ from atdd.coach.commands.registry import RegistryLoader
94
+
95
+ loader = RegistryLoader(temp_repo)
96
+ result = loader.load_all()
97
+
98
+ assert "plan" in result
99
+ assert "contracts" in result
100
+ assert "telemetry" in result
101
+ assert isinstance(result, dict)
102
+
103
+ # Verify read-only (no files modified)
104
+ assert sample_wagons_registry.exists()
105
+
106
+
107
+ # SPEC-COACH-UTILS-0201
108
+ def test_build_all_registries(temp_repo, sample_wagon_manifest):
109
+ """
110
+ Test that registry --build updates all registries from source files.
111
+
112
+ Given: User runs registry --build
113
+ When: Registry builder executes
114
+ Then: Builds/updates manifests, shows preview, asks confirmation
115
+ """
116
+ from atdd.coach.commands.registry import RegistryBuilder
117
+
118
+ builder = RegistryBuilder(temp_repo)
119
+
120
+ # Mock user confirmation
121
+ with patch('builtins.input', return_value='yes'):
122
+ result = builder.build_all()
123
+
124
+ assert "plan" in result
125
+ assert result["plan"]["total_manifests"] >= 1
126
+
127
+ # Verify registry was created
128
+ wagons_registry = temp_repo / "plan" / "_wagons.yaml"
129
+ assert wagons_registry.exists()
130
+
131
+
132
+ # SPEC-COACH-UTILS-0202
133
+ def test_load_planner_registry(temp_repo, sample_wagons_registry):
134
+ """
135
+ Test that registry --planner loads planner registry only.
136
+
137
+ Given: User runs registry --planner
138
+ When: Registry loader executes
139
+ Then: Loads only plan/_wagons.yaml, returns wagon entries
140
+ """
141
+ from atdd.coach.commands.registry import RegistryLoader
142
+
143
+ loader = RegistryLoader(temp_repo)
144
+ result = loader.load_planner()
145
+
146
+ assert "wagons" in result
147
+ assert len(result["wagons"]) >= 1
148
+ assert result["wagons"][0]["wagon"] == "existing-wagon"
149
+
150
+ # Should not load other registries
151
+ assert "contracts" not in result
152
+ assert "telemetry" not in result
153
+
154
+
155
+ # SPEC-COACH-UTILS-0203
156
+ def test_build_planner_registry(temp_repo, sample_wagon_manifest, sample_wagons_registry):
157
+ """
158
+ Test that registry --planner --build updates planner registry from wagon manifests.
159
+
160
+ Given: User runs registry --planner --build
161
+ When: Registry builder executes
162
+ Then: Scans wagon manifests, updates registry, preserves drafts
163
+ """
164
+ from atdd.coach.commands.registry import RegistryBuilder
165
+
166
+ builder = RegistryBuilder(temp_repo)
167
+
168
+ with patch('builtins.input', return_value='yes'):
169
+ result = builder.build_planner(preview_only=False)
170
+
171
+ assert result["total_manifests"] >= 1
172
+ assert result["new"] >= 0
173
+ assert result["updated"] >= 0
174
+ assert result["preserved_drafts"] >= 1 # draft-wagon should be preserved
175
+
176
+ # Verify draft wagon preserved
177
+ with open(sample_wagons_registry) as f:
178
+ registry = yaml.safe_load(f)
179
+
180
+ draft_wagons = [w for w in registry["wagons"] if w["wagon"] == "draft-wagon"]
181
+ assert len(draft_wagons) == 1
182
+
183
+
184
+ # SPEC-COACH-UTILS-0204
185
+ def test_load_coder_registry(temp_repo):
186
+ """
187
+ Test that registry --coder loads coder implementation registry.
188
+
189
+ Given: User runs registry --coder
190
+ When: Registry loader executes
191
+ Then: Loads python/_implementations.yaml with URNs and links
192
+ """
193
+ from atdd.coach.commands.registry import RegistryLoader
194
+
195
+ # Create sample implementations registry
196
+ impl_registry = {
197
+ "implementations": [
198
+ {
199
+ "urn": "impl:test-wagon:domain:entity:python",
200
+ "file": "python/test_wagon/src/domain/entities/test.py",
201
+ "spec_urn": "spec:test-wagon:feature",
202
+ "test_urn": "test:test-wagon:test_entity.py::test_entity_creation",
203
+ "wagon": "test-wagon",
204
+ "layer": "domain",
205
+ "language": "python"
206
+ }
207
+ ]
208
+ }
209
+
210
+ impl_path = temp_repo / "python" / "_implementations.yaml"
211
+ with open(impl_path, "w") as f:
212
+ yaml.dump(impl_registry, f)
213
+
214
+ loader = RegistryLoader(temp_repo)
215
+ result = loader.load_coder()
216
+
217
+ assert "implementations" in result
218
+ assert len(result["implementations"]) == 1
219
+ assert result["implementations"][0]["urn"].startswith("impl:")
220
+ assert "spec_urn" in result["implementations"][0]
221
+ assert "test_urn" in result["implementations"][0]
222
+
223
+
224
+ # SPEC-COACH-UTILS-0205
225
+ def test_build_coder_registry(temp_repo):
226
+ """
227
+ Test that registry --coder --build creates implementation registry from Python files.
228
+
229
+ Given: User runs registry --coder --build
230
+ When: Registry builder executes
231
+ Then: Scans Python files, extracts metadata, generates URNs, creates manifest
232
+ """
233
+ from atdd.coach.commands.registry import RegistryBuilder
234
+
235
+ # Create sample Python implementation
236
+ py_file = temp_repo / "python" / "test_wagon" / "src" / "domain" / "entities" / "test.py"
237
+ py_file.parent.mkdir(parents=True, exist_ok=True)
238
+
239
+ py_content = '''"""
240
+ Test entity.
241
+
242
+ Spec: spec:test-wagon:feature
243
+ Test: test:test-wagon:test_entity.py::test_entity_creation
244
+ """
245
+
246
+ class TestEntity:
247
+ """Domain entity for testing."""
248
+ pass
249
+ '''
250
+
251
+ py_file.write_text(py_content)
252
+
253
+ builder = RegistryBuilder(temp_repo)
254
+
255
+ with patch('builtins.input', return_value='yes'):
256
+ result = builder.build_coder(preview_only=False)
257
+
258
+ assert result["processed"] >= 1
259
+
260
+ # Verify registry created
261
+ impl_registry = temp_repo / "python" / "_implementations.yaml"
262
+ assert impl_registry.exists()
263
+
264
+ with open(impl_registry) as f:
265
+ data = yaml.safe_load(f)
266
+
267
+ assert "implementations" in data
268
+ assert len(data["implementations"]) >= 1
269
+
270
+
271
+ # SPEC-COACH-UTILS-0206
272
+ def test_draft_mode_preserves_entries(temp_repo, sample_wagons_registry):
273
+ """
274
+ Test that draft mode preserves registry entries without physical source files.
275
+
276
+ Given: Registry has entry without path/manifest field (draft)
277
+ When: registry --build executes
278
+ Then: Preserves draft entries, marks in change report
279
+ """
280
+ from atdd.coach.commands.registry import RegistryBuilder
281
+
282
+ builder = RegistryBuilder(temp_repo)
283
+
284
+ with patch('builtins.input', return_value='yes'):
285
+ result = builder.build_planner(preview_only=False)
286
+
287
+ # Verify draft wagon preserved
288
+ assert result["preserved_drafts"] >= 1
289
+
290
+ with open(sample_wagons_registry) as f:
291
+ registry = yaml.safe_load(f)
292
+
293
+ # draft-wagon should still exist
294
+ wagons = {w["wagon"]: w for w in registry["wagons"]}
295
+ assert "draft-wagon" in wagons
296
+ assert "manifest" not in wagons["draft-wagon"]
297
+
298
+
299
+ # SPEC-COACH-UTILS-0207
300
+ def test_spec_test_impl_traceability(temp_repo):
301
+ """
302
+ Test that spec-test-impl traceability links artifacts across registries.
303
+
304
+ Given: Multiple registries with URN links
305
+ When: Building unified registry view
306
+ Then: Each impl links to spec_urn and test_urn, enables traceability queries
307
+ """
308
+ from atdd.coach.commands.registry import RegistryLoader
309
+
310
+ # Create linked registries
311
+ spec_urn = "spec:test-wagon:feature"
312
+ test_urn = "test:test-wagon:test_feature.py::test_feature_works"
313
+ impl_urn = "impl:test-wagon:domain:entity:python"
314
+
315
+ # Implementation registry with links
316
+ impl_registry = {
317
+ "implementations": [{
318
+ "urn": impl_urn,
319
+ "spec_urn": spec_urn,
320
+ "test_urn": test_urn,
321
+ "wagon": "test-wagon"
322
+ }]
323
+ }
324
+
325
+ impl_path = temp_repo / "python" / "_implementations.yaml"
326
+ with open(impl_path, "w") as f:
327
+ yaml.dump(impl_registry, f)
328
+
329
+ loader = RegistryLoader(temp_repo)
330
+
331
+ # Query traceability
332
+ impl_for_spec = loader.find_implementations_for_spec(spec_urn)
333
+ assert len(impl_for_spec) == 1
334
+ assert impl_for_spec[0]["urn"] == impl_urn
335
+
336
+ tests_for_impl = loader.find_tests_for_implementation(impl_urn)
337
+ assert tests_for_impl == test_urn
338
+
339
+
340
+ # SPEC-COACH-UTILS-0208
341
+ def test_autogenerate_manifest_for_new_registry(temp_repo):
342
+ """
343
+ Test that registry auto-generates manifest for new registry types.
344
+
345
+ Given: New registry type has no manifest (e.g., supabase/)
346
+ When: registry --build executes
347
+ Then: Detects missing manifest, scans sources, auto-generates manifest
348
+ """
349
+ from atdd.coach.commands.registry import RegistryBuilder
350
+
351
+ # Create supabase function without registry
352
+ func_file = temp_repo / "supabase" / "functions" / "test-func" / "index.ts"
353
+ func_file.parent.mkdir(parents=True, exist_ok=True)
354
+ func_file.write_text("export const handler = () => {}")
355
+
356
+ builder = RegistryBuilder(temp_repo)
357
+
358
+ with patch('builtins.input', return_value='yes'):
359
+ result = builder.build_supabase(preview_only=False)
360
+
361
+ # Verify manifest created
362
+ supabase_registry = temp_repo / "supabase" / "_functions.yaml"
363
+ assert supabase_registry.exists()
364
+
365
+ with open(supabase_registry) as f:
366
+ data = yaml.safe_load(f)
367
+
368
+ assert "functions" in data
369
+
370
+
371
+ # SPEC-COACH-UTILS-0209
372
+ def test_detect_field_level_changes(temp_repo, sample_wagon_manifest, sample_wagons_registry):
373
+ """
374
+ Test that registry detects and reports field-level changes.
375
+
376
+ Given: Existing registry with entries, source files have updated fields
377
+ When: registry --build executes
378
+ Then: Compares field-by-field, reports changed fields, shows detailed report
379
+ """
380
+ from atdd.coach.commands.registry import RegistryBuilder
381
+
382
+ # First, add test-wagon to existing registry
383
+ with open(sample_wagons_registry) as f:
384
+ registry = yaml.safe_load(f)
385
+
386
+ registry["wagons"].append({
387
+ "wagon": "test-wagon",
388
+ "description": "Original description",
389
+ "path": "plan/test-wagon/",
390
+ "manifest": str(sample_wagon_manifest.relative_to(temp_repo))
391
+ })
392
+
393
+ with open(sample_wagons_registry, "w") as f:
394
+ yaml.dump(registry, f)
395
+
396
+ # Update wagon manifest description
397
+ with open(sample_wagon_manifest) as f:
398
+ manifest = yaml.safe_load(f)
399
+
400
+ manifest["description"] = "Updated description for test wagon"
401
+
402
+ with open(sample_wagon_manifest, "w") as f:
403
+ yaml.dump(manifest, f)
404
+
405
+ builder = RegistryBuilder(temp_repo)
406
+
407
+ # Preview mode to see changes
408
+ result = builder.build_planner(preview_only=True)
409
+
410
+ assert "changes" in result
411
+
412
+ # Find change for test-wagon
413
+ test_wagon_changes = [c for c in result["changes"] if c.get("wagon") == "test-wagon"]
414
+
415
+ assert len(test_wagon_changes) > 0
416
+ assert "fields" in test_wagon_changes[0]
417
+ assert "description" in test_wagon_changes[0]["fields"]
418
+
419
+
420
+ # SPEC-COACH-UTILS-0210
421
+ def test_registry_command_renamed():
422
+ """
423
+ Test that registry command is renamed from registry_updater.
424
+
425
+ Given: Old command location atdd/coach/commands/registry_updater.py
426
+ When: Refactoring to unified registry system
427
+ Then: Command renamed to registry.py, maintains backward compatibility
428
+ """
429
+ from atdd.coach.commands import registry
430
+
431
+ # Verify new module exists
432
+ assert hasattr(registry, 'RegistryLoader')
433
+ assert hasattr(registry, 'RegistryBuilder')
434
+
435
+ # Verify backward compatibility (should be able to import old names)
436
+ assert hasattr(registry, 'RegistryUpdater') or hasattr(registry, 'RegistryBuilder')
437
+
438
+
439
+ # SPEC-COACH-UTILS-0211
440
+ def test_load_tester_registry(temp_repo):
441
+ """
442
+ Test that registry --tester loads tester test registry.
443
+
444
+ Given: User runs registry --tester
445
+ When: Registry loader executes
446
+ Then: Loads atdd/tester/_tests.yaml with URNs and links
447
+ """
448
+ from atdd.coach.commands.registry import RegistryLoader
449
+
450
+ # Create sample test registry
451
+ test_registry = {
452
+ "tests": [
453
+ {
454
+ "urn": "test:test-wagon:test_feature.py::test_feature_works",
455
+ "file": "atdd/tester/test_wagon/test_feature.py",
456
+ "spec_urn": "spec:test-wagon:feature",
457
+ "wagon": "test-wagon",
458
+ "acceptance_urn": "acceptance:test-wagon:EXEC001"
459
+ }
460
+ ]
461
+ }
462
+
463
+ test_path = temp_repo / "atdd" / "tester" / "_tests.yaml"
464
+ with open(test_path, "w") as f:
465
+ yaml.dump(test_registry, f)
466
+
467
+ loader = RegistryLoader(temp_repo)
468
+ result = loader.load_tester()
469
+
470
+ assert "tests" in result
471
+ assert len(result["tests"]) == 1
472
+ assert result["tests"][0]["urn"].startswith("test:")
473
+ assert "spec_urn" in result["tests"][0]
474
+ assert "acceptance_urn" in result["tests"][0]
475
+
476
+
477
+ # SPEC-COACH-UTILS-0212
478
+ def test_build_tester_registry(temp_repo):
479
+ """
480
+ Test that registry --tester --build creates test registry from test files.
481
+
482
+ Given: User runs registry --tester --build
483
+ When: Registry builder executes
484
+ Then: Scans test files, extracts metadata, generates URNs, creates manifest
485
+ """
486
+ from atdd.coach.commands.registry import RegistryBuilder
487
+
488
+ # Create sample test file with URN marker
489
+ test_file = temp_repo / "atdd" / "tester" / "test_wagon" / "test_feature.py"
490
+ test_file.parent.mkdir(parents=True, exist_ok=True)
491
+
492
+ test_content = '''"""
493
+ Test feature.
494
+
495
+ URN: test:test-wagon:test_feature.py::test_feature_works
496
+ Spec: spec:test-wagon:feature
497
+ Acceptance: acceptance:test-wagon:EXEC001
498
+ """
499
+ import pytest
500
+
501
+ def test_feature_works():
502
+ """Test that feature works correctly."""
503
+ assert True
504
+ '''
505
+
506
+ test_file.write_text(test_content)
507
+
508
+ builder = RegistryBuilder(temp_repo)
509
+
510
+ with patch('builtins.input', return_value='yes'):
511
+ result = builder.build_tester(preview_only=False)
512
+
513
+ assert result["processed"] >= 1
514
+
515
+ # Verify registry created
516
+ test_registry = temp_repo / "atdd" / "tester" / "_tests.yaml"
517
+ assert test_registry.exists()
518
+
519
+ with open(test_registry) as f:
520
+ data = yaml.safe_load(f)
521
+
522
+ assert "tests" in data
523
+
524
+
525
+ # SPEC-COACH-UTILS-0213
526
+ def test_registry_builder_dry_composition(temp_repo):
527
+ """
528
+ Test that registry builder extracts common pattern for DRY composition.
529
+
530
+ Given: Existing registry_updater has similar update methods
531
+ When: Refactoring to unified registry system
532
+ Then: Extracts RegistryBuilder base class, shares common logic
533
+ """
534
+ from atdd.coach.commands.registry import RegistryBuilder
535
+
536
+ builder = RegistryBuilder(temp_repo)
537
+
538
+ # Verify builder has common methods
539
+ assert hasattr(builder, 'build_planner')
540
+ assert hasattr(builder, 'build_coder')
541
+ assert hasattr(builder, 'build_tester')
542
+
543
+ # Verify common preview/confirmation logic exists
544
+ assert hasattr(builder, '_print_change_report') or hasattr(builder, 'preview_changes')
545
+ assert hasattr(builder, '_detect_changes') or hasattr(builder, 'detect_changes')
546
+
547
+ # Each builder method should follow same pattern
548
+ # (scan, extract, build, preview, confirm, write)
549
+
550
+
551
+ # SPEC-COACH-UTILS-0214
552
+ def test_new_registries_follow_urn_conventions(temp_repo):
553
+ """
554
+ Test that new registries follow existing URN conventions.
555
+
556
+ Given: Existing URN patterns in urn.py
557
+ When: Building new registries (tester, coder)
558
+ Then: Uses URNBuilder for URN generation and validation
559
+ """
560
+ from atdd.coach.commands.registry import RegistryBuilder
561
+
562
+ # Mock URNBuilder to verify it's used
563
+ with patch('atdd.coach.commands.registry.URNBuilder') as mock_urn:
564
+ mock_urn.test.return_value = "test:wagon:file::func"
565
+ mock_urn.impl.return_value = "impl:wagon:layer:comp:lang"
566
+
567
+ builder = RegistryBuilder(temp_repo)
568
+
569
+ # Should use URNBuilder for test URNs
570
+ test_urn = mock_urn.test("wagon", "file", "func")
571
+ assert test_urn.startswith("test:")
572
+
573
+ # Should use URNBuilder for impl URNs
574
+ impl_urn = mock_urn.impl("wagon", "layer", "comp", "lang")
575
+ assert impl_urn.startswith("impl:")