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,593 @@
1
+ """
2
+ Test and auto-fix URN headers in initialization/barrel files.
3
+
4
+ Validates and fixes:
5
+ - Python __init__.py files: URN comment + package docstring
6
+ - Dart index.dart files: URN comment + export documentation
7
+ - TypeScript index.ts files: URN comment + module documentation
8
+
9
+ Convention:
10
+ - All init/barrel files must have URN header
11
+ - URN format: urn:jel:{wagon}:{component}:{layer}:{sublayer}...
12
+ - URN derived from file path structure
13
+
14
+ Auto-fix Strategy:
15
+ - Generate URN from file path
16
+ - Add appropriate language-specific comment
17
+ - Add package/module docstring
18
+ - Preserve existing code (imports/exports)
19
+ """
20
+
21
+ import pytest
22
+ import re
23
+ from pathlib import Path
24
+ from typing import List, Tuple, Optional
25
+
26
+
27
+ # Path constants
28
+ REPO_ROOT = Path(__file__).resolve().parents[3]
29
+ PYTHON_DIR = REPO_ROOT / "python"
30
+ DART_DIR = REPO_ROOT / "lib"
31
+ TS_DIR = REPO_ROOT / "typescript"
32
+
33
+
34
+ def find_python_init_files() -> List[Path]:
35
+ """Find all Python __init__.py files."""
36
+ if not PYTHON_DIR.exists():
37
+ return []
38
+
39
+ return list(PYTHON_DIR.rglob("__init__.py"))
40
+
41
+
42
+ def find_dart_index_files() -> List[Path]:
43
+ """Find all Dart index.dart barrel files."""
44
+ if not DART_DIR.exists():
45
+ return []
46
+
47
+ return list(DART_DIR.rglob("index.dart"))
48
+
49
+
50
+ def find_ts_index_files() -> List[Path]:
51
+ """Find all TypeScript index.ts barrel files."""
52
+ if not TS_DIR.exists():
53
+ return []
54
+
55
+ index_files = []
56
+ index_files.extend(TS_DIR.rglob("index.ts"))
57
+ index_files.extend(TS_DIR.rglob("index.tsx"))
58
+ return index_files
59
+
60
+
61
+ def generate_urn_from_path(file_path: Path, language: str) -> str:
62
+ """
63
+ Generate URN from file path.
64
+
65
+ Examples:
66
+ - python/pace_dilemmas/pair_fragments/src/domain/services/__init__.py
67
+ → urn:jel:pace-dilemmas:pair-fragments:domain:services
68
+
69
+ - lib/maintain_ux/provide_foundations/index.dart
70
+ → urn:jel:maintain-ux:provide-foundations
71
+
72
+ - typescript/play_match/initialize_session/src/domain/index.ts
73
+ → urn:jel:play-match:initialize-session:domain
74
+ """
75
+ parts = file_path.parts
76
+
77
+ # Find language root index
78
+ try:
79
+ if language == "python":
80
+ lang_idx = parts.index("python")
81
+ elif language == "dart":
82
+ lang_idx = parts.index("lib")
83
+ elif language == "typescript":
84
+ lang_idx = parts.index("typescript")
85
+ else:
86
+ return ""
87
+ except ValueError:
88
+ return ""
89
+
90
+ # Extract path components after language root
91
+ path_components = parts[lang_idx + 1:]
92
+
93
+ # Remove filename and 'src' directories
94
+ filtered_components = []
95
+ for comp in path_components:
96
+ if comp in ["__init__.py", "index.dart", "index.ts", "index.tsx"]:
97
+ continue
98
+ if comp == "src":
99
+ continue
100
+ # Convert underscores to hyphens for kebab-case
101
+ comp_kebab = comp.replace("_", "-")
102
+ filtered_components.append(comp_kebab)
103
+
104
+ # Build URN
105
+ if not filtered_components:
106
+ return ""
107
+
108
+ urn = "urn:jel:" + ":".join(filtered_components)
109
+ return urn
110
+
111
+
112
+ def extract_urn_from_file(file_path: Path, language: str) -> Optional[str]:
113
+ """Extract URN from file header."""
114
+ try:
115
+ with open(file_path, 'r', encoding='utf-8') as f:
116
+ lines = f.readlines()
117
+ except Exception:
118
+ return None
119
+
120
+ comment_prefix = "#" if language == "python" else "//"
121
+
122
+ for line in lines[:10]: # Check first 10 lines
123
+ stripped = line.strip()
124
+ if stripped.startswith(comment_prefix):
125
+ # Match: # urn:jel:... or // urn:jel:...
126
+ match = re.match(rf'{re.escape(comment_prefix)}\s*urn:jel:(.+)', stripped)
127
+ if match:
128
+ return f"urn:jel:{match.group(1).strip()}"
129
+
130
+ return None
131
+
132
+
133
+ def get_package_description(file_path: Path, urn: str) -> str:
134
+ """Generate appropriate package description from URN."""
135
+ # Extract last component as package name
136
+ components = urn.split(":")
137
+ if len(components) < 3:
138
+ return "Package exports."
139
+
140
+ last_component = components[-1]
141
+ package_name = last_component.replace("-", "_")
142
+
143
+ # Check if this is a layer name
144
+ layer_names = {
145
+ "domain": "Domain layer",
146
+ "application": "Application layer",
147
+ "presentation": "Presentation layer",
148
+ "integration": "Integration layer",
149
+ "entities": "Entity definitions",
150
+ "services": "Domain services",
151
+ "use-cases": "Use case implementations",
152
+ "ports": "Port interfaces",
153
+ "controllers": "Controller implementations",
154
+ "repositories": "Repository implementations",
155
+ "adapters": "Adapter implementations",
156
+ "mappers": "Mapper implementations",
157
+ "engines": "Engine implementations",
158
+ "queries": "Query implementations",
159
+ "validators": "Validator implementations",
160
+ }
161
+
162
+ if last_component in layer_names:
163
+ return f"{layer_names[last_component]}."
164
+
165
+ # Get parent component for context
166
+ if len(components) >= 4:
167
+ parent = components[-2]
168
+ return f"{last_component.replace('-', ' ').title()} for {parent.replace('-', ' ')} component."
169
+
170
+ return f"{package_name.replace('_', ' ').title()} package."
171
+
172
+
173
+ def fix_python_init_file(file_path: Path) -> bool:
174
+ """
175
+ Add URN header and docstring to Python __init__.py file.
176
+
177
+ Returns:
178
+ True if file was modified, False otherwise
179
+ """
180
+ # Generate expected URN
181
+ expected_urn = generate_urn_from_path(file_path, "python")
182
+ if not expected_urn:
183
+ return False
184
+
185
+ # Check current URN
186
+ current_urn = extract_urn_from_file(file_path, "python")
187
+
188
+ # Read current content
189
+ try:
190
+ with open(file_path, 'r', encoding='utf-8') as f:
191
+ current_content = f.read()
192
+ except Exception:
193
+ return False
194
+
195
+ # Check if already has correct URN and docstring
196
+ has_urn = current_urn == expected_urn
197
+ has_docstring = '"""' in current_content or "'''" in current_content
198
+
199
+ if has_urn and has_docstring:
200
+ return False # Already correct
201
+
202
+ # Generate package description
203
+ description = get_package_description(file_path, expected_urn)
204
+
205
+ # Build new header
206
+ header_parts = []
207
+
208
+ # Add URN comment
209
+ if not has_urn:
210
+ header_parts.append(f"# {expected_urn}")
211
+
212
+ # Add docstring
213
+ if not has_docstring:
214
+ header_parts.append(f'"""{description}"""')
215
+
216
+ # Combine header with existing content
217
+ if header_parts:
218
+ # Remove old URN if exists
219
+ lines = current_content.split('\n')
220
+ cleaned_lines = []
221
+ for line in lines:
222
+ # Skip old URN comments
223
+ if line.strip().startswith("# urn:jel:"):
224
+ continue
225
+ cleaned_lines.append(line)
226
+
227
+ cleaned_content = '\n'.join(cleaned_lines).lstrip('\n')
228
+
229
+ new_content = '\n'.join(header_parts) + '\n'
230
+ if cleaned_content:
231
+ new_content += '\n' + cleaned_content
232
+
233
+ # Write updated content
234
+ try:
235
+ with open(file_path, 'w', encoding='utf-8') as f:
236
+ f.write(new_content)
237
+ return True
238
+ except Exception:
239
+ return False
240
+
241
+ return False
242
+
243
+
244
+ def fix_dart_index_file(file_path: Path) -> bool:
245
+ """
246
+ Add URN header and documentation to Dart index.dart file.
247
+
248
+ Returns:
249
+ True if file was modified, False otherwise
250
+ """
251
+ # Generate expected URN
252
+ expected_urn = generate_urn_from_path(file_path, "dart")
253
+ if not expected_urn:
254
+ return False
255
+
256
+ # Check current URN
257
+ current_urn = extract_urn_from_file(file_path, "dart")
258
+
259
+ # Read current content
260
+ try:
261
+ with open(file_path, 'r', encoding='utf-8') as f:
262
+ current_content = f.read()
263
+ except Exception:
264
+ return False
265
+
266
+ # Check if already has correct URN
267
+ if current_urn == expected_urn:
268
+ return False # Already correct
269
+
270
+ # Generate module description
271
+ description = get_package_description(file_path, expected_urn)
272
+
273
+ # Build new header
274
+ header = f"// {expected_urn}\n/// {description}\n"
275
+
276
+ # Remove old URN if exists
277
+ lines = current_content.split('\n')
278
+ cleaned_lines = []
279
+ for line in lines:
280
+ # Skip old URN comments
281
+ if line.strip().startswith("// urn:jel:"):
282
+ continue
283
+ # Skip old documentation comments at the start
284
+ if not cleaned_lines and line.strip().startswith("///"):
285
+ continue
286
+ cleaned_lines.append(line)
287
+
288
+ cleaned_content = '\n'.join(cleaned_lines).lstrip('\n')
289
+
290
+ new_content = header
291
+ if cleaned_content:
292
+ new_content += '\n' + cleaned_content
293
+
294
+ # Write updated content
295
+ try:
296
+ with open(file_path, 'w', encoding='utf-8') as f:
297
+ f.write(new_content)
298
+ return True
299
+ except Exception:
300
+ return False
301
+
302
+
303
+ def fix_ts_index_file(file_path: Path) -> bool:
304
+ """
305
+ Add URN header and documentation to TypeScript index.ts file.
306
+
307
+ Returns:
308
+ True if file was modified, False otherwise
309
+ """
310
+ # Generate expected URN
311
+ expected_urn = generate_urn_from_path(file_path, "typescript")
312
+ if not expected_urn:
313
+ return False
314
+
315
+ # Check current URN
316
+ current_urn = extract_urn_from_file(file_path, "typescript")
317
+
318
+ # Read current content
319
+ try:
320
+ with open(file_path, 'r', encoding='utf-8') as f:
321
+ current_content = f.read()
322
+ except Exception:
323
+ return False
324
+
325
+ # Check if already has correct URN
326
+ if current_urn == expected_urn:
327
+ return False # Already correct
328
+
329
+ # Generate module description
330
+ description = get_package_description(file_path, expected_urn)
331
+
332
+ # Build new header
333
+ header = f"// {expected_urn}\n/** {description} */\n"
334
+
335
+ # Remove old URN if exists
336
+ lines = current_content.split('\n')
337
+ cleaned_lines = []
338
+ for line in lines:
339
+ # Skip old URN comments
340
+ if line.strip().startswith("// urn:jel:"):
341
+ continue
342
+ cleaned_lines.append(line)
343
+
344
+ cleaned_content = '\n'.join(cleaned_lines).lstrip('\n')
345
+
346
+ new_content = header
347
+ if cleaned_content:
348
+ new_content += '\n' + cleaned_content
349
+
350
+ # Write updated content
351
+ try:
352
+ with open(file_path, 'w', encoding='utf-8') as f:
353
+ f.write(new_content)
354
+ return True
355
+ except Exception:
356
+ return False
357
+
358
+
359
+ @pytest.mark.coder
360
+ def test_python_init_files_have_urns():
361
+ """
362
+ SPEC-CODER-URN-0001: Python __init__.py files have URN headers.
363
+
364
+ All __init__.py files must have:
365
+ - URN comment header (# urn:jel:...)
366
+ - Package docstring
367
+
368
+ Auto-fix: Adds missing URN and docstring
369
+
370
+ Given: All Python __init__.py files
371
+ When: Checking for URN headers
372
+ Then: All files have correct URN and docstring
373
+ """
374
+ init_files = find_python_init_files()
375
+
376
+ if not init_files:
377
+ pytest.skip("No Python __init__.py files found")
378
+
379
+ missing_urns = []
380
+ fixed_files = []
381
+
382
+ for init_file in init_files:
383
+ expected_urn = generate_urn_from_path(init_file, "python")
384
+ if not expected_urn:
385
+ continue
386
+
387
+ current_urn = extract_urn_from_file(init_file, "python")
388
+
389
+ # Try to read content for docstring check
390
+ try:
391
+ with open(init_file, 'r', encoding='utf-8') as f:
392
+ content = f.read()
393
+ has_docstring = '"""' in content or "'''" in content
394
+ except Exception:
395
+ has_docstring = False
396
+
397
+ if current_urn != expected_urn or not has_docstring:
398
+ # Auto-fix
399
+ if fix_python_init_file(init_file):
400
+ fixed_files.append(init_file)
401
+ else:
402
+ missing_urns.append((init_file, expected_urn, current_urn))
403
+
404
+ # Report results
405
+ if fixed_files:
406
+ rel_paths = [f.relative_to(REPO_ROOT) for f in fixed_files]
407
+ print(f"\n✅ Auto-fixed {len(fixed_files)} Python __init__.py files:")
408
+ for path in rel_paths[:10]:
409
+ print(f" {path}")
410
+ if len(rel_paths) > 10:
411
+ print(f" ... and {len(rel_paths) - 10} more")
412
+
413
+ if missing_urns:
414
+ pytest.fail(
415
+ f"\n\nFound {len(missing_urns)} Python __init__.py files that could not be fixed:\n\n" +
416
+ "\n".join(
417
+ f" {file.relative_to(REPO_ROOT)}\n"
418
+ f" Expected: {expected}\n"
419
+ f" Current: {current or 'None'}"
420
+ for file, expected, current in missing_urns[:10]
421
+ ) +
422
+ (f"\n\n... and {len(missing_urns) - 10} more" if len(missing_urns) > 10 else "")
423
+ )
424
+
425
+
426
+ @pytest.mark.coder
427
+ def test_dart_index_files_have_urns():
428
+ """
429
+ SPEC-CODER-URN-0002: Dart index.dart files have URN headers.
430
+
431
+ All index.dart barrel files must have:
432
+ - URN comment header (// urn:jel:...)
433
+ - Module documentation (///)
434
+
435
+ Auto-fix: Adds missing URN and documentation
436
+
437
+ Given: All Dart index.dart files
438
+ When: Checking for URN headers
439
+ Then: All files have correct URN and documentation
440
+ """
441
+ index_files = find_dart_index_files()
442
+
443
+ if not index_files:
444
+ pytest.skip("No Dart index.dart files found")
445
+
446
+ missing_urns = []
447
+ fixed_files = []
448
+
449
+ for index_file in index_files:
450
+ expected_urn = generate_urn_from_path(index_file, "dart")
451
+ if not expected_urn:
452
+ continue
453
+
454
+ current_urn = extract_urn_from_file(index_file, "dart")
455
+
456
+ if current_urn != expected_urn:
457
+ # Auto-fix
458
+ if fix_dart_index_file(index_file):
459
+ fixed_files.append(index_file)
460
+ else:
461
+ missing_urns.append((index_file, expected_urn, current_urn))
462
+
463
+ # Report results
464
+ if fixed_files:
465
+ rel_paths = [f.relative_to(REPO_ROOT) for f in fixed_files]
466
+ print(f"\n✅ Auto-fixed {len(fixed_files)} Dart index.dart files:")
467
+ for path in rel_paths[:10]:
468
+ print(f" {path}")
469
+ if len(rel_paths) > 10:
470
+ print(f" ... and {len(rel_paths) - 10} more")
471
+
472
+ if missing_urns:
473
+ pytest.fail(
474
+ f"\n\nFound {len(missing_urns)} Dart index.dart files that could not be fixed:\n\n" +
475
+ "\n".join(
476
+ f" {file.relative_to(REPO_ROOT)}\n"
477
+ f" Expected: {expected}\n"
478
+ f" Current: {current or 'None'}"
479
+ for file, expected, current in missing_urns[:10]
480
+ ) +
481
+ (f"\n\n... and {len(missing_urns) - 10} more" if len(missing_urns) > 10 else "")
482
+ )
483
+
484
+
485
+ @pytest.mark.coder
486
+ def test_typescript_index_files_have_urns():
487
+ """
488
+ SPEC-CODER-URN-0003: TypeScript index.ts files have URN headers.
489
+
490
+ All index.ts/tsx barrel files must have:
491
+ - URN comment header (// urn:jel:...)
492
+ - Module documentation (/** ... */)
493
+
494
+ Auto-fix: Adds missing URN and documentation
495
+
496
+ Given: All TypeScript index.ts/tsx files
497
+ When: Checking for URN headers
498
+ Then: All files have correct URN and documentation
499
+ """
500
+ index_files = find_ts_index_files()
501
+
502
+ if not index_files:
503
+ pytest.skip("No TypeScript index.ts/tsx files found")
504
+
505
+ missing_urns = []
506
+ fixed_files = []
507
+
508
+ for index_file in index_files:
509
+ expected_urn = generate_urn_from_path(index_file, "typescript")
510
+ if not expected_urn:
511
+ continue
512
+
513
+ current_urn = extract_urn_from_file(index_file, "typescript")
514
+
515
+ if current_urn != expected_urn:
516
+ # Auto-fix
517
+ if fix_ts_index_file(index_file):
518
+ fixed_files.append(index_file)
519
+ else:
520
+ missing_urns.append((index_file, expected_urn, current_urn))
521
+
522
+ # Report results
523
+ if fixed_files:
524
+ rel_paths = [f.relative_to(REPO_ROOT) for f in fixed_files]
525
+ print(f"\n✅ Auto-fixed {len(fixed_files)} TypeScript index files:")
526
+ for path in rel_paths[:10]:
527
+ print(f" {path}")
528
+ if len(rel_paths) > 10:
529
+ print(f" ... and {len(rel_paths) - 10} more")
530
+
531
+ if missing_urns:
532
+ pytest.fail(
533
+ f"\n\nFound {len(missing_urns)} TypeScript index files that could not be fixed:\n\n" +
534
+ "\n".join(
535
+ f" {file.relative_to(REPO_ROOT)}\n"
536
+ f" Expected: {expected}\n"
537
+ f" Current: {current or 'None'}"
538
+ for file, expected, current in missing_urns[:10]
539
+ ) +
540
+ (f"\n\n... and {len(missing_urns) - 10} more" if len(missing_urns) > 10 else "")
541
+ )
542
+
543
+
544
+ @pytest.mark.coder
545
+ def test_urn_generation_logic():
546
+ """
547
+ SPEC-CODER-URN-0004: URN generation logic is correct.
548
+
549
+ Validate URN generation from various file paths.
550
+
551
+ Given: Sample file paths
552
+ When: Generating URNs
553
+ Then: URNs match expected format
554
+ """
555
+ test_cases = [
556
+ # (file_path, language, expected_urn)
557
+ ("python/pace_dilemmas/pair_fragments/src/domain/services/__init__.py",
558
+ "python",
559
+ "urn:jel:pace-dilemmas:pair-fragments:domain:services"),
560
+
561
+ ("python/pace_dilemmas/pair_fragments/src/domain/__init__.py",
562
+ "python",
563
+ "urn:jel:pace-dilemmas:pair-fragments:domain"),
564
+
565
+ ("lib/maintain_ux/provide_foundations/index.dart",
566
+ "dart",
567
+ "urn:jel:maintain-ux:provide-foundations"),
568
+
569
+ ("typescript/play_match/initialize_session/src/domain/index.ts",
570
+ "typescript",
571
+ "urn:jel:play-match:initialize-session:domain"),
572
+ ]
573
+
574
+ failures = []
575
+
576
+ for path_str, language, expected in test_cases:
577
+ # Create a Path object from the string
578
+ test_path = REPO_ROOT / path_str
579
+
580
+ actual = generate_urn_from_path(test_path, language)
581
+
582
+ if actual != expected:
583
+ failures.append(
584
+ f"Path: {path_str}\n"
585
+ f" Expected: {expected}\n"
586
+ f" Actual: {actual}"
587
+ )
588
+
589
+ if failures:
590
+ pytest.fail(
591
+ f"\n\nURN generation logic failures:\n\n" +
592
+ "\n\n".join(failures)
593
+ )