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,431 @@
1
+ """
2
+ Test cross-language consistency between Python, Dart, and TypeScript.
3
+
4
+ Validates:
5
+ - Entities defined consistently across languages
6
+ - Enums match across languages
7
+ - Value object structures align
8
+ - API contracts are honored
9
+
10
+ Inspired by: .claude/utils/coder/ (multiple utilities)
11
+ But: Self-contained, no utility dependencies
12
+ """
13
+
14
+ import pytest
15
+ import re
16
+ import json
17
+ from pathlib import Path
18
+ from typing import Dict, List, Set
19
+
20
+
21
+ # Path constants
22
+ REPO_ROOT = Path(__file__).resolve().parents[3]
23
+ PYTHON_DIR = REPO_ROOT / "python"
24
+ LIB_DIR = REPO_ROOT / "lib"
25
+ SUPABASE_DIR = REPO_ROOT / "supabase"
26
+ CONTRACTS_DIR = REPO_ROOT / "contracts"
27
+
28
+
29
+ def extract_python_classes() -> Dict[str, Dict]:
30
+ """
31
+ Extract class definitions from Python code.
32
+
33
+ Returns:
34
+ Dict mapping class name to metadata
35
+ """
36
+ if not PYTHON_DIR.exists():
37
+ return {}
38
+
39
+ classes = {}
40
+
41
+ for py_file in PYTHON_DIR.rglob("*.py"):
42
+ if '/test/' in str(py_file):
43
+ continue
44
+
45
+ try:
46
+ with open(py_file, 'r', encoding='utf-8') as f:
47
+ content = f.read()
48
+ except Exception:
49
+ continue
50
+
51
+ # Find class definitions
52
+ class_pattern = r'class\s+([A-Z][a-zA-Z0-9_]*)\s*[:\(]'
53
+ for match in re.finditer(class_pattern, content):
54
+ class_name = match.group(1)
55
+ classes[class_name] = {
56
+ 'file': str(py_file.relative_to(REPO_ROOT)),
57
+ 'language': 'python'
58
+ }
59
+
60
+ return classes
61
+
62
+
63
+ def extract_dart_classes() -> Dict[str, Dict]:
64
+ """
65
+ Extract class definitions from Dart code.
66
+
67
+ Returns:
68
+ Dict mapping class name to metadata
69
+ """
70
+ if not LIB_DIR.exists():
71
+ return {}
72
+
73
+ classes = {}
74
+
75
+ for dart_file in LIB_DIR.rglob("*.dart"):
76
+ if dart_file.name.endswith('_test.dart'):
77
+ continue
78
+
79
+ try:
80
+ with open(dart_file, 'r', encoding='utf-8') as f:
81
+ content = f.read()
82
+ except Exception:
83
+ continue
84
+
85
+ # Find class definitions
86
+ class_pattern = r'class\s+([A-Z][a-zA-Z0-9_]*)\s*[{\s]'
87
+ for match in re.finditer(class_pattern, content):
88
+ class_name = match.group(1)
89
+ classes[class_name] = {
90
+ 'file': str(dart_file.relative_to(REPO_ROOT)),
91
+ 'language': 'dart'
92
+ }
93
+
94
+ return classes
95
+
96
+
97
+ def extract_python_enums() -> Dict[str, Set[str]]:
98
+ """
99
+ Extract enum definitions from Python code.
100
+
101
+ Returns:
102
+ Dict mapping enum name to set of values
103
+ """
104
+ if not PYTHON_DIR.exists():
105
+ return {}
106
+
107
+ enums = {}
108
+
109
+ for py_file in PYTHON_DIR.rglob("*.py"):
110
+ if '/test/' in str(py_file):
111
+ continue
112
+
113
+ try:
114
+ with open(py_file, 'r', encoding='utf-8') as f:
115
+ content = f.read()
116
+ except Exception:
117
+ continue
118
+
119
+ # Find enum definitions: class XxxEnum(Enum)
120
+ enum_pattern = r'class\s+([A-Z][a-zA-Z0-9_]*)\s*\(\s*Enum\s*\):'
121
+ for match in re.finditer(enum_pattern, content):
122
+ enum_name = match.group(1)
123
+
124
+ # Extract enum values (simplified)
125
+ # Pattern: NAME = value
126
+ value_pattern = r'^\s+([A-Z_]+)\s*='
127
+ values = set()
128
+ for line in content.split('\n'):
129
+ val_match = re.match(value_pattern, line)
130
+ if val_match:
131
+ values.add(val_match.group(1))
132
+
133
+ if values:
134
+ enums[enum_name] = values
135
+
136
+ return enums
137
+
138
+
139
+ def extract_dart_enums() -> Dict[str, Set[str]]:
140
+ """
141
+ Extract enum definitions from Dart code.
142
+
143
+ Returns:
144
+ Dict mapping enum name to set of values
145
+ """
146
+ if not LIB_DIR.exists():
147
+ return {}
148
+
149
+ enums = {}
150
+
151
+ for dart_file in LIB_DIR.rglob("*.dart"):
152
+ if dart_file.name.endswith('_test.dart'):
153
+ continue
154
+
155
+ try:
156
+ with open(dart_file, 'r', encoding='utf-8') as f:
157
+ content = f.read()
158
+ except Exception:
159
+ continue
160
+
161
+ # Find enum definitions: enum XxxEnum { ... }
162
+ enum_pattern = r'enum\s+([A-Z][a-zA-Z0-9_]*)\s*\{([^}]+)\}'
163
+ for match in re.finditer(enum_pattern, content):
164
+ enum_name = match.group(1)
165
+ enum_body = match.group(2)
166
+
167
+ # Extract values
168
+ values = set()
169
+ for value in enum_body.split(','):
170
+ val = value.strip()
171
+ if val and not val.startswith('//'):
172
+ # Remove any comments
173
+ val = val.split('//')[0].strip()
174
+ if val:
175
+ values.add(val)
176
+
177
+ if values:
178
+ enums[enum_name] = values
179
+
180
+ return enums
181
+
182
+
183
+ def find_contract_entities() -> Dict[str, Set[str]]:
184
+ """
185
+ Extract entities from contract schemas.
186
+
187
+ Returns:
188
+ Dict mapping entity name to set of fields
189
+ """
190
+ if not CONTRACTS_DIR.exists():
191
+ return {}
192
+
193
+ entities = {}
194
+
195
+ for schema_file in CONTRACTS_DIR.rglob("*.schema.json"):
196
+ try:
197
+ with open(schema_file, 'r', encoding='utf-8') as f:
198
+ schema = json.load(f)
199
+ except Exception:
200
+ continue
201
+
202
+ # Extract entity name from $id
203
+ schema_id = schema.get('$id', '')
204
+ if ':' in schema_id:
205
+ entity_name = schema_id.split(':')[-1].replace('.', '_')
206
+ else:
207
+ entity_name = schema_file.stem
208
+
209
+ # Extract required fields
210
+ required = set(schema.get('required', []))
211
+ properties = set(schema.get('properties', {}).keys())
212
+
213
+ entities[entity_name] = required if required else properties
214
+
215
+ return entities
216
+
217
+
218
+ @pytest.mark.coder
219
+ def test_entity_classes_exist_across_languages():
220
+ """
221
+ SPEC-CODER-CONSISTENCY-0001: Core entities exist in all languages.
222
+
223
+ For polyglot codebases, core domain entities should exist in all languages.
224
+
225
+ Given: Entity classes in Python, Dart, contracts
226
+ When: Comparing entity names
227
+ Then: Core entities exist across languages
228
+ """
229
+ python_classes = extract_python_classes()
230
+ dart_classes = extract_dart_classes()
231
+ contract_entities = find_contract_entities()
232
+
233
+ if not python_classes and not dart_classes:
234
+ pytest.skip("No classes found")
235
+
236
+ if not contract_entities:
237
+ pytest.skip("No contract entities found")
238
+
239
+ # Check if contract entities have implementations
240
+ missing_implementations = []
241
+
242
+ for entity_name, fields in contract_entities.items():
243
+ # Normalize name (PascalCase)
244
+ normalized = ''.join(word.capitalize() for word in entity_name.split('_'))
245
+
246
+ # Check Python
247
+ has_python = normalized in python_classes or entity_name in python_classes
248
+ # Check Dart
249
+ has_dart = normalized in dart_classes or entity_name in dart_classes
250
+
251
+ if not has_python and not has_dart:
252
+ missing_implementations.append(
253
+ f"Contract entity: {entity_name}\\n"
254
+ f" Fields: {', '.join(list(fields)[:5])}\\n"
255
+ f" Missing in: Python AND Dart"
256
+ )
257
+ elif not has_python:
258
+ missing_implementations.append(
259
+ f"Contract entity: {entity_name}\\n"
260
+ f" Missing in: Python"
261
+ )
262
+ elif not has_dart:
263
+ missing_implementations.append(
264
+ f"Contract entity: {entity_name}\\n"
265
+ f" Missing in: Dart"
266
+ )
267
+
268
+ if missing_implementations:
269
+ pytest.fail(
270
+ f"\\n\\nFound {len(missing_implementations)} contract entities without implementations:\\n\\n" +
271
+ "\\n\\n".join(missing_implementations[:10]) +
272
+ (f"\\n\\n... and {len(missing_implementations) - 10} more" if len(missing_implementations) > 10 else "")
273
+ )
274
+
275
+
276
+ @pytest.mark.coder
277
+ def test_enums_match_across_languages():
278
+ """
279
+ SPEC-CODER-CONSISTENCY-0002: Enums match across languages.
280
+
281
+ Enums should have same values in all languages.
282
+
283
+ Given: Enum definitions in Python and Dart
284
+ When: Comparing enum values
285
+ Then: Enums with same name have same values
286
+ """
287
+ python_enums = extract_python_enums()
288
+ dart_enums = extract_dart_enums()
289
+
290
+ if not python_enums and not dart_enums:
291
+ pytest.skip("No enums found")
292
+
293
+ mismatches = []
294
+
295
+ # Find enums with same name
296
+ for enum_name in set(python_enums.keys()) & set(dart_enums.keys()):
297
+ python_values = python_enums[enum_name]
298
+ dart_values = dart_enums[enum_name]
299
+
300
+ # Compare (case-insensitive)
301
+ python_lower = {v.lower() for v in python_values}
302
+ dart_lower = {v.lower() for v in dart_values}
303
+
304
+ if python_lower != dart_lower:
305
+ only_python = python_lower - dart_lower
306
+ only_dart = dart_lower - python_lower
307
+
308
+ mismatches.append(
309
+ f"Enum: {enum_name}\\n"
310
+ f" Only in Python: {', '.join(only_python) if only_python else 'none'}\\n"
311
+ f" Only in Dart: {', '.join(only_dart) if only_dart else 'none'}"
312
+ )
313
+
314
+ if mismatches:
315
+ pytest.fail(
316
+ f"\\n\\nFound {len(mismatches)} enum mismatches:\\n\\n" +
317
+ "\\n\\n".join(mismatches)
318
+ )
319
+
320
+
321
+ @pytest.mark.coder
322
+ def test_naming_conventions_consistent():
323
+ """
324
+ SPEC-CODER-CONSISTENCY-0003: Naming conventions are consistent.
325
+
326
+ Similar concepts should use similar names across languages.
327
+
328
+ Given: Class names in all languages
329
+ When: Comparing patterns
330
+ Then: Consistent naming (e.g., XxxEntity vs Xxx)
331
+ """
332
+ python_classes = extract_python_classes()
333
+ dart_classes = extract_dart_classes()
334
+
335
+ if not python_classes or not dart_classes:
336
+ pytest.skip("Need classes in multiple languages")
337
+
338
+ # Check for common suffixes
339
+ python_suffixes = {}
340
+ dart_suffixes = {}
341
+
342
+ for name in python_classes.keys():
343
+ for suffix in ['Entity', 'Model', 'DTO', 'Service', 'Repository']:
344
+ if name.endswith(suffix):
345
+ base = name[:-len(suffix)]
346
+ python_suffixes.setdefault(suffix, set()).add(base)
347
+
348
+ for name in dart_classes.keys():
349
+ for suffix in ['Entity', 'Model', 'DTO', 'Service', 'Repository']:
350
+ if name.endswith(suffix):
351
+ base = name[:-len(suffix)]
352
+ dart_suffixes.setdefault(suffix, set()).add(base)
353
+
354
+ # Find inconsistencies
355
+ inconsistencies = []
356
+
357
+ for suffix in set(python_suffixes.keys()) | set(dart_suffixes.keys()):
358
+ python_bases = python_suffixes.get(suffix, set())
359
+ dart_bases = dart_suffixes.get(suffix, set())
360
+
361
+ # Find classes that use different suffixes
362
+ common_bases = python_bases & dart_bases
363
+
364
+ for base in common_bases:
365
+ # If same base exists with this suffix in both, good
366
+ pass
367
+
368
+ # Check if base exists with different suffix
369
+ for base in python_bases:
370
+ # Check if Dart has same base with different suffix
371
+ for dart_suffix in dart_suffixes.keys():
372
+ if suffix != dart_suffix and base in dart_suffixes[dart_suffix]:
373
+ inconsistencies.append(
374
+ f"Base class: {base}\\n"
375
+ f" Python uses: {suffix}\\n"
376
+ f" Dart uses: {dart_suffix}"
377
+ )
378
+
379
+ if inconsistencies:
380
+ pytest.fail(
381
+ f"\\n\\nFound {len(inconsistencies)} naming inconsistencies:\\n\\n" +
382
+ "\\n\\n".join(inconsistencies[:10]) +
383
+ (f"\\n\\n... and {len(inconsistencies) - 10} more" if len(inconsistencies) > 10 else "")
384
+ )
385
+
386
+
387
+ @pytest.mark.coder
388
+ def test_api_contracts_honored_across_languages():
389
+ """
390
+ SPEC-CODER-CONSISTENCY-0004: API contracts honored in all implementations.
391
+
392
+ Contract schemas define API structure.
393
+ All language implementations should follow them.
394
+
395
+ Given: Contract schemas
396
+ When: Checking for matching entities
397
+ Then: Each language has entity matching contract
398
+ """
399
+ contract_entities = find_contract_entities()
400
+ python_classes = extract_python_classes()
401
+ dart_classes = extract_dart_classes()
402
+
403
+ if not contract_entities:
404
+ pytest.skip("No contracts found")
405
+
406
+ # For each contract, check if at least one language implements it
407
+ unimplemented = []
408
+
409
+ for entity_name, fields in contract_entities.items():
410
+ normalized = ''.join(word.capitalize() for word in entity_name.split('_'))
411
+
412
+ has_any_impl = (
413
+ normalized in python_classes or
414
+ entity_name in python_classes or
415
+ normalized in dart_classes or
416
+ entity_name in dart_classes
417
+ )
418
+
419
+ if not has_any_impl:
420
+ unimplemented.append(
421
+ f"Contract: {entity_name}\\n"
422
+ f" Fields: {', '.join(list(fields)[:5])}\\n"
423
+ f" No implementations found"
424
+ )
425
+
426
+ if unimplemented:
427
+ pytest.fail(
428
+ f"\\n\\nFound {len(unimplemented)} unimplemented contracts:\\n\\n" +
429
+ "\\n\\n".join(unimplemented[:10]) +
430
+ (f"\\n\\n... and {len(unimplemented) - 10} more" if len(unimplemented) > 10 else "")
431
+ )