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.
- atdd/__init__.py +0 -0
- atdd/cli.py +404 -0
- atdd/coach/__init__.py +0 -0
- atdd/coach/commands/__init__.py +0 -0
- atdd/coach/commands/add_persistence_metadata.py +215 -0
- atdd/coach/commands/analyze_migrations.py +188 -0
- atdd/coach/commands/consumers.py +720 -0
- atdd/coach/commands/infer_governance_status.py +149 -0
- atdd/coach/commands/initializer.py +177 -0
- atdd/coach/commands/interface.py +1078 -0
- atdd/coach/commands/inventory.py +565 -0
- atdd/coach/commands/migration.py +240 -0
- atdd/coach/commands/registry.py +1560 -0
- atdd/coach/commands/session.py +430 -0
- atdd/coach/commands/sync.py +405 -0
- atdd/coach/commands/test_interface.py +399 -0
- atdd/coach/commands/test_runner.py +141 -0
- atdd/coach/commands/tests/__init__.py +1 -0
- atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
- atdd/coach/commands/traceability.py +4264 -0
- atdd/coach/conventions/session.convention.yaml +754 -0
- atdd/coach/overlays/__init__.py +2 -0
- atdd/coach/overlays/claude.md +2 -0
- atdd/coach/schemas/config.schema.json +34 -0
- atdd/coach/schemas/manifest.schema.json +101 -0
- atdd/coach/templates/ATDD.md +282 -0
- atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
- atdd/coach/utils/__init__.py +0 -0
- atdd/coach/utils/graph/__init__.py +0 -0
- atdd/coach/utils/graph/urn.py +875 -0
- atdd/coach/validators/__init__.py +0 -0
- atdd/coach/validators/shared_fixtures.py +365 -0
- atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
- atdd/coach/validators/test_registry.py +575 -0
- atdd/coach/validators/test_session_validation.py +1183 -0
- atdd/coach/validators/test_traceability.py +448 -0
- atdd/coach/validators/test_update_feature_paths.py +108 -0
- atdd/coach/validators/test_validate_contract_consumers.py +297 -0
- atdd/coder/__init__.py +1 -0
- atdd/coder/conventions/adapter.recipe.yaml +88 -0
- atdd/coder/conventions/backend.convention.yaml +460 -0
- atdd/coder/conventions/boundaries.convention.yaml +666 -0
- atdd/coder/conventions/commons.convention.yaml +460 -0
- atdd/coder/conventions/complexity.recipe.yaml +109 -0
- atdd/coder/conventions/component-naming.convention.yaml +178 -0
- atdd/coder/conventions/design.convention.yaml +327 -0
- atdd/coder/conventions/design.recipe.yaml +273 -0
- atdd/coder/conventions/dto.convention.yaml +660 -0
- atdd/coder/conventions/frontend.convention.yaml +542 -0
- atdd/coder/conventions/green.convention.yaml +1012 -0
- atdd/coder/conventions/presentation.convention.yaml +587 -0
- atdd/coder/conventions/refactor.convention.yaml +535 -0
- atdd/coder/conventions/technology.convention.yaml +206 -0
- atdd/coder/conventions/tests/__init__.py +0 -0
- atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
- atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
- atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
- atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
- atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
- atdd/coder/conventions/thinness.recipe.yaml +82 -0
- atdd/coder/conventions/train.convention.yaml +325 -0
- atdd/coder/conventions/verification.protocol.yaml +53 -0
- atdd/coder/schemas/design_system.schema.json +361 -0
- atdd/coder/validators/__init__.py +0 -0
- atdd/coder/validators/test_commons_structure.py +485 -0
- atdd/coder/validators/test_complexity.py +416 -0
- atdd/coder/validators/test_cross_language_consistency.py +431 -0
- atdd/coder/validators/test_design_system_compliance.py +413 -0
- atdd/coder/validators/test_dto_testing_patterns.py +268 -0
- atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
- atdd/coder/validators/test_green_layer_dependencies.py +148 -0
- atdd/coder/validators/test_green_python_layer_structure.py +103 -0
- atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
- atdd/coder/validators/test_import_boundaries.py +396 -0
- atdd/coder/validators/test_init_file_urns.py +593 -0
- atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
- atdd/coder/validators/test_presentation_convention.py +260 -0
- atdd/coder/validators/test_python_architecture.py +674 -0
- atdd/coder/validators/test_quality_metrics.py +420 -0
- atdd/coder/validators/test_station_master_pattern.py +244 -0
- atdd/coder/validators/test_train_infrastructure.py +454 -0
- atdd/coder/validators/test_train_urns.py +293 -0
- atdd/coder/validators/test_typescript_architecture.py +616 -0
- atdd/coder/validators/test_usecase_structure.py +421 -0
- atdd/coder/validators/test_wagon_boundaries.py +586 -0
- atdd/conftest.py +126 -0
- atdd/planner/__init__.py +1 -0
- atdd/planner/conventions/acceptance.convention.yaml +538 -0
- atdd/planner/conventions/appendix.convention.yaml +187 -0
- atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
- atdd/planner/conventions/component.convention.yaml +670 -0
- atdd/planner/conventions/criteria.convention.yaml +141 -0
- atdd/planner/conventions/feature.convention.yaml +371 -0
- atdd/planner/conventions/interface.convention.yaml +382 -0
- atdd/planner/conventions/steps.convention.yaml +141 -0
- atdd/planner/conventions/train.convention.yaml +552 -0
- atdd/planner/conventions/wagon.convention.yaml +275 -0
- atdd/planner/conventions/wmbt.convention.yaml +258 -0
- atdd/planner/schemas/acceptance.schema.json +336 -0
- atdd/planner/schemas/appendix.schema.json +78 -0
- atdd/planner/schemas/component.schema.json +114 -0
- atdd/planner/schemas/feature.schema.json +197 -0
- atdd/planner/schemas/train.schema.json +192 -0
- atdd/planner/schemas/wagon.schema.json +281 -0
- atdd/planner/schemas/wmbt.schema.json +59 -0
- atdd/planner/validators/__init__.py +0 -0
- atdd/planner/validators/conftest.py +5 -0
- atdd/planner/validators/test_draft_wagon_registry.py +374 -0
- atdd/planner/validators/test_plan_cross_refs.py +240 -0
- atdd/planner/validators/test_plan_uniqueness.py +224 -0
- atdd/planner/validators/test_plan_urn_resolution.py +268 -0
- atdd/planner/validators/test_plan_wagons.py +174 -0
- atdd/planner/validators/test_train_validation.py +514 -0
- atdd/planner/validators/test_wagon_urn_chain.py +648 -0
- atdd/planner/validators/test_wmbt_consistency.py +327 -0
- atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
- atdd/tester/__init__.py +1 -0
- atdd/tester/conventions/artifact.convention.yaml +257 -0
- atdd/tester/conventions/contract.convention.yaml +1009 -0
- atdd/tester/conventions/filename.convention.yaml +555 -0
- atdd/tester/conventions/migration.convention.yaml +509 -0
- atdd/tester/conventions/red.convention.yaml +797 -0
- atdd/tester/conventions/routing.convention.yaml +51 -0
- atdd/tester/conventions/telemetry.convention.yaml +458 -0
- atdd/tester/schemas/a11y.tmpl.json +17 -0
- atdd/tester/schemas/artifact.schema.json +189 -0
- atdd/tester/schemas/contract.schema.json +591 -0
- atdd/tester/schemas/contract.tmpl.json +95 -0
- atdd/tester/schemas/db.tmpl.json +20 -0
- atdd/tester/schemas/e2e.tmpl.json +17 -0
- atdd/tester/schemas/edge_function.tmpl.json +17 -0
- atdd/tester/schemas/event.tmpl.json +17 -0
- atdd/tester/schemas/http.tmpl.json +19 -0
- atdd/tester/schemas/job.tmpl.json +18 -0
- atdd/tester/schemas/load.tmpl.json +21 -0
- atdd/tester/schemas/metric.tmpl.json +19 -0
- atdd/tester/schemas/pack.schema.json +139 -0
- atdd/tester/schemas/realtime.tmpl.json +20 -0
- atdd/tester/schemas/rls.tmpl.json +18 -0
- atdd/tester/schemas/script.tmpl.json +16 -0
- atdd/tester/schemas/sec.tmpl.json +18 -0
- atdd/tester/schemas/storage.tmpl.json +18 -0
- atdd/tester/schemas/telemetry.schema.json +128 -0
- atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
- atdd/tester/schemas/test_filename.schema.json +194 -0
- atdd/tester/schemas/test_intent.schema.json +179 -0
- atdd/tester/schemas/unit.tmpl.json +18 -0
- atdd/tester/schemas/visual.tmpl.json +18 -0
- atdd/tester/schemas/ws.tmpl.json +17 -0
- atdd/tester/utils/__init__.py +0 -0
- atdd/tester/utils/filename.py +300 -0
- atdd/tester/validators/__init__.py +0 -0
- atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
- atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
- atdd/tester/validators/conftest.py +5 -0
- atdd/tester/validators/coverage_gap_report.py +321 -0
- atdd/tester/validators/fix_dual_ac_references.py +179 -0
- atdd/tester/validators/remove_duplicate_lines.py +93 -0
- atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
- atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
- atdd/tester/validators/test_artifact_naming_category.py +307 -0
- atdd/tester/validators/test_contract_schema_compliance.py +706 -0
- atdd/tester/validators/test_contracts_structure.py +200 -0
- atdd/tester/validators/test_coverage_adequacy.py +797 -0
- atdd/tester/validators/test_dual_ac_reference.py +225 -0
- atdd/tester/validators/test_fixture_validity.py +372 -0
- atdd/tester/validators/test_isolation.py +487 -0
- atdd/tester/validators/test_migration_coverage.py +204 -0
- atdd/tester/validators/test_migration_criteria.py +276 -0
- atdd/tester/validators/test_migration_generation.py +116 -0
- atdd/tester/validators/test_python_test_naming.py +410 -0
- atdd/tester/validators/test_red_layer_validation.py +95 -0
- atdd/tester/validators/test_red_python_layer_structure.py +87 -0
- atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
- atdd/tester/validators/test_telemetry_structure.py +634 -0
- atdd/tester/validators/test_typescript_test_naming.py +301 -0
- atdd/tester/validators/test_typescript_test_structure.py +84 -0
- atdd-0.1.0.dist-info/METADATA +191 -0
- atdd-0.1.0.dist-info/RECORD +183 -0
- atdd-0.1.0.dist-info/WHEEL +5 -0
- atdd-0.1.0.dist-info/entry_points.txt +2 -0
- atdd-0.1.0.dist-info/licenses/LICENSE +674 -0
- atdd-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test train URN validation for theme orchestrators.
|
|
3
|
+
|
|
4
|
+
Validates conventions from:
|
|
5
|
+
- atdd/planner/conventions/train.convention.yaml
|
|
6
|
+
|
|
7
|
+
Enforces:
|
|
8
|
+
- Theme orchestrators have train URNs (python/shared/{theme}.py)
|
|
9
|
+
- URN format: train:{theme}:{train_id}
|
|
10
|
+
- Train IDs exist in plan/_trains/*.yaml
|
|
11
|
+
- Each train file has corresponding implementation
|
|
12
|
+
|
|
13
|
+
Rationale:
|
|
14
|
+
Theme orchestrators in python/shared/ implement workflows defined in train specs.
|
|
15
|
+
URNs provide bidirectional traceability between implementation and specification.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
import re
|
|
20
|
+
import yaml
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import List, Dict, Set, Tuple
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Path constants
|
|
26
|
+
REPO_ROOT = Path(__file__).resolve().parents[4]
|
|
27
|
+
PYTHON_SHARED_DIR = REPO_ROOT / "python" / "shared"
|
|
28
|
+
TRAINS_DIR = REPO_ROOT / "plan" / "_trains"
|
|
29
|
+
TRAIN_CONVENTION = REPO_ROOT / "atdd" / "planner" / "conventions" / "train.convention.yaml"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def find_theme_orchestrators() -> List[Path]:
|
|
33
|
+
"""Find all theme orchestrator files in python/shared/."""
|
|
34
|
+
if not PYTHON_SHARED_DIR.exists():
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
orchestrators = []
|
|
38
|
+
for py_file in PYTHON_SHARED_DIR.glob("*.py"):
|
|
39
|
+
# Skip __init__.py and utility files
|
|
40
|
+
if py_file.name in ["__init__.py", "conftest.py"]:
|
|
41
|
+
continue
|
|
42
|
+
# Skip files in subdirectories
|
|
43
|
+
if not py_file.parent == PYTHON_SHARED_DIR:
|
|
44
|
+
continue
|
|
45
|
+
orchestrators.append(py_file)
|
|
46
|
+
|
|
47
|
+
return orchestrators
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def extract_train_urns(file_path: Path) -> List[str]:
|
|
51
|
+
"""Extract train URNs from file header comments."""
|
|
52
|
+
urns = []
|
|
53
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
54
|
+
for line in f:
|
|
55
|
+
# Stop at first non-comment line after shebang
|
|
56
|
+
stripped = line.strip()
|
|
57
|
+
if not stripped:
|
|
58
|
+
continue
|
|
59
|
+
if not stripped.startswith('#'):
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
# Match: # urn: train:{theme}:{train_id}
|
|
63
|
+
match = re.match(r'#\s*urn:\s*train:([^:]+):(.+)', stripped)
|
|
64
|
+
if match:
|
|
65
|
+
theme = match.group(1)
|
|
66
|
+
train_id = match.group(2).strip()
|
|
67
|
+
urns.append(f"train:{theme}:{train_id}")
|
|
68
|
+
|
|
69
|
+
return urns
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def find_all_train_specs() -> Dict[str, Path]:
|
|
73
|
+
"""Find all train specification YAML files."""
|
|
74
|
+
train_specs = {}
|
|
75
|
+
if not TRAINS_DIR.exists():
|
|
76
|
+
return train_specs
|
|
77
|
+
|
|
78
|
+
for yaml_file in TRAINS_DIR.glob("*.yaml"):
|
|
79
|
+
# Train ID is the filename without extension
|
|
80
|
+
train_id = yaml_file.stem
|
|
81
|
+
train_specs[train_id] = yaml_file
|
|
82
|
+
|
|
83
|
+
return train_specs
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def load_train_spec(train_file: Path) -> Dict:
|
|
87
|
+
"""Load train specification from YAML."""
|
|
88
|
+
with open(train_file, 'r', encoding='utf-8') as f:
|
|
89
|
+
return yaml.safe_load(f)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_theme_orchestrators_exist():
|
|
93
|
+
"""Theme orchestrators should exist in python/shared/ directory."""
|
|
94
|
+
orchestrators = find_theme_orchestrators()
|
|
95
|
+
|
|
96
|
+
assert len(orchestrators) > 0, (
|
|
97
|
+
"No theme orchestrators found in python/shared/. "
|
|
98
|
+
"Expected files like mechanic.py, match.py, etc."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_theme_orchestrators_have_train_urns():
|
|
103
|
+
"""
|
|
104
|
+
Theme orchestrators must have train URN headers.
|
|
105
|
+
|
|
106
|
+
Expected format:
|
|
107
|
+
# urn: train:{theme}:{train_id}
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
# urn: train:match:3001-match-setup-standard
|
|
111
|
+
# urn: train:match:3004-match-completion-standard
|
|
112
|
+
"""
|
|
113
|
+
orchestrators = find_theme_orchestrators()
|
|
114
|
+
missing_urns = []
|
|
115
|
+
|
|
116
|
+
for orch_file in orchestrators:
|
|
117
|
+
urns = extract_train_urns(orch_file)
|
|
118
|
+
if not urns:
|
|
119
|
+
missing_urns.append(orch_file.name)
|
|
120
|
+
|
|
121
|
+
if missing_urns:
|
|
122
|
+
pytest.fail(
|
|
123
|
+
f"\nFound {len(missing_urns)} theme orchestrators without train URNs:\n\n" +
|
|
124
|
+
"\n".join(f" {name}\n Missing: # urn: train:{{theme}}:{{train_id}}"
|
|
125
|
+
for name in missing_urns) +
|
|
126
|
+
"\n\nSee: atdd/planner/conventions/train.convention.yaml::theme_orchestrator_urn"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_train_urns_match_convention_format():
|
|
131
|
+
"""
|
|
132
|
+
Train URNs must follow format: train:{theme}:{train_id}
|
|
133
|
+
|
|
134
|
+
Theme should match filename (e.g., match.py → train:match:...)
|
|
135
|
+
Train ID should match pattern: DDDD-kebab-case-name
|
|
136
|
+
"""
|
|
137
|
+
orchestrators = find_theme_orchestrators()
|
|
138
|
+
format_violations = []
|
|
139
|
+
|
|
140
|
+
for orch_file in orchestrators:
|
|
141
|
+
urns = extract_train_urns(orch_file)
|
|
142
|
+
expected_theme = orch_file.stem # filename without .py
|
|
143
|
+
|
|
144
|
+
for urn in urns:
|
|
145
|
+
# Parse URN
|
|
146
|
+
match = re.match(r'train:([^:]+):(.+)', urn)
|
|
147
|
+
if not match:
|
|
148
|
+
format_violations.append((orch_file.name, urn, "Invalid URN format"))
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
theme = match.group(1)
|
|
152
|
+
train_id = match.group(2)
|
|
153
|
+
|
|
154
|
+
# Validate theme matches filename
|
|
155
|
+
if theme != expected_theme:
|
|
156
|
+
format_violations.append((
|
|
157
|
+
orch_file.name,
|
|
158
|
+
urn,
|
|
159
|
+
f"Theme '{theme}' doesn't match filename '{expected_theme}.py'"
|
|
160
|
+
))
|
|
161
|
+
|
|
162
|
+
# Validate train_id format (DDDD-kebab-case)
|
|
163
|
+
if not re.match(r'^\d{4}-[a-z][a-z0-9-]*$', train_id):
|
|
164
|
+
format_violations.append((
|
|
165
|
+
orch_file.name,
|
|
166
|
+
urn,
|
|
167
|
+
f"Train ID '{train_id}' doesn't match pattern: DDDD-kebab-case-name"
|
|
168
|
+
))
|
|
169
|
+
|
|
170
|
+
if format_violations:
|
|
171
|
+
pytest.fail(
|
|
172
|
+
f"\nFound {len(format_violations)} train URN format violations:\n\n" +
|
|
173
|
+
"\n".join(f" {file}\n URN: {urn}\n Issue: {issue}"
|
|
174
|
+
for file, urn, issue in format_violations) +
|
|
175
|
+
"\n\nExpected format: train:{theme}:{train_id}"
|
|
176
|
+
"\nExample: train:match:3001-match-setup-standard"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_train_urns_reference_existing_specs():
|
|
181
|
+
"""
|
|
182
|
+
Train URNs must reference train specs that exist in plan/_trains/.
|
|
183
|
+
|
|
184
|
+
For each URN train:{theme}:{train_id}, verify:
|
|
185
|
+
- plan/_trains/{train_id}.yaml exists
|
|
186
|
+
- Train spec has matching train_id field
|
|
187
|
+
"""
|
|
188
|
+
orchestrators = find_theme_orchestrators()
|
|
189
|
+
train_specs = find_all_train_specs()
|
|
190
|
+
missing_specs = []
|
|
191
|
+
|
|
192
|
+
for orch_file in orchestrators:
|
|
193
|
+
urns = extract_train_urns(orch_file)
|
|
194
|
+
|
|
195
|
+
for urn in urns:
|
|
196
|
+
# Extract train_id from URN
|
|
197
|
+
match = re.match(r'train:([^:]+):(.+)', urn)
|
|
198
|
+
if not match:
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
theme = match.group(1)
|
|
202
|
+
train_id = match.group(2)
|
|
203
|
+
|
|
204
|
+
# Check if train spec exists
|
|
205
|
+
if train_id not in train_specs:
|
|
206
|
+
missing_specs.append((orch_file.name, urn, train_id))
|
|
207
|
+
|
|
208
|
+
if missing_specs:
|
|
209
|
+
pytest.fail(
|
|
210
|
+
f"\nFound {len(missing_specs)} train URNs referencing non-existent specs:\n\n" +
|
|
211
|
+
"\n".join(f" {file}\n URN: {urn}\n Missing: plan/_trains/{train_id}.yaml"
|
|
212
|
+
for file, urn, train_id in missing_specs) +
|
|
213
|
+
"\n\nEither:\n"
|
|
214
|
+
" 1. Create the missing train spec in plan/_trains/\n"
|
|
215
|
+
" 2. Fix the URN to reference an existing train"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_train_specs_have_implementations():
|
|
220
|
+
"""
|
|
221
|
+
All train specs in plan/_trains/ should have implementations.
|
|
222
|
+
|
|
223
|
+
This is a reverse check: warn about train specs without orchestrators.
|
|
224
|
+
Not a hard failure, but helps identify missing implementations.
|
|
225
|
+
"""
|
|
226
|
+
train_specs = find_all_train_specs()
|
|
227
|
+
orchestrators = find_theme_orchestrators()
|
|
228
|
+
|
|
229
|
+
# Collect all implemented train IDs
|
|
230
|
+
implemented_trains = set()
|
|
231
|
+
for orch_file in orchestrators:
|
|
232
|
+
urns = extract_train_urns(orch_file)
|
|
233
|
+
for urn in urns:
|
|
234
|
+
match = re.match(r'train:([^:]+):(.+)', urn)
|
|
235
|
+
if match:
|
|
236
|
+
train_id = match.group(2)
|
|
237
|
+
implemented_trains.add(train_id)
|
|
238
|
+
|
|
239
|
+
# Find unimplemented trains
|
|
240
|
+
unimplemented = []
|
|
241
|
+
for train_id, train_file in train_specs.items():
|
|
242
|
+
if train_id not in implemented_trains:
|
|
243
|
+
# Load spec to get theme
|
|
244
|
+
spec = load_train_spec(train_file)
|
|
245
|
+
themes = spec.get('themes', [])
|
|
246
|
+
theme_str = themes[0] if themes else "unknown"
|
|
247
|
+
unimplemented.append((train_id, theme_str))
|
|
248
|
+
|
|
249
|
+
# This is just a warning, not a failure
|
|
250
|
+
if unimplemented:
|
|
251
|
+
# Group by theme
|
|
252
|
+
by_theme = {}
|
|
253
|
+
for train_id, theme in unimplemented:
|
|
254
|
+
if theme not in by_theme:
|
|
255
|
+
by_theme[theme] = []
|
|
256
|
+
by_theme[theme].append(train_id)
|
|
257
|
+
|
|
258
|
+
message = f"\nℹ️ Found {len(unimplemented)} train specs without implementations:\n\n"
|
|
259
|
+
for theme, trains in sorted(by_theme.items()):
|
|
260
|
+
message += f"\n {theme} theme:\n"
|
|
261
|
+
for train_id in sorted(trains):
|
|
262
|
+
message += f" - {train_id}\n"
|
|
263
|
+
|
|
264
|
+
message += (
|
|
265
|
+
f"\nTotal: {len(unimplemented)} trains\n"
|
|
266
|
+
f"Implemented: {len(implemented_trains)} trains\n"
|
|
267
|
+
f"Coverage: {len(implemented_trains)}/{len(train_specs)} "
|
|
268
|
+
f"({100*len(implemented_trains)//len(train_specs) if train_specs else 0}%)\n"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Print info but don't fail
|
|
272
|
+
print(message)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_train_convention_file_exists():
|
|
276
|
+
"""Train convention file should exist and be valid YAML."""
|
|
277
|
+
assert TRAIN_CONVENTION.exists(), (
|
|
278
|
+
f"Train convention file not found: {TRAIN_CONVENTION}\n"
|
|
279
|
+
"Expected: atdd/planner/conventions/train.convention.yaml"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Validate it's valid YAML
|
|
283
|
+
with open(TRAIN_CONVENTION, 'r', encoding='utf-8') as f:
|
|
284
|
+
convention = yaml.safe_load(f)
|
|
285
|
+
|
|
286
|
+
assert convention is not None, "Train convention file is empty or invalid YAML"
|
|
287
|
+
|
|
288
|
+
# Check for theme_orchestrator_urn section
|
|
289
|
+
urn_naming = convention.get('urn_naming', {})
|
|
290
|
+
assert 'theme_orchestrator_urn' in urn_naming, (
|
|
291
|
+
"Train convention missing 'urn_naming.theme_orchestrator_urn' section\n"
|
|
292
|
+
"Expected format documentation for train:{theme}:{train_id}"
|
|
293
|
+
)
|