atdd 0.5.0__py3-none-any.whl → 0.6.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.
@@ -58,6 +58,69 @@
58
58
  },
59
59
  "required": ["last_version"],
60
60
  "additionalProperties": false
61
+ },
62
+ "coverage": {
63
+ "type": "object",
64
+ "description": "Hierarchy coverage validation settings (ATDD Hierarchy Coverage Spec v0.1)",
65
+ "properties": {
66
+ "exceptions": {
67
+ "type": "object",
68
+ "description": "Allow-lists for coverage exceptions",
69
+ "properties": {
70
+ "wagons_not_in_train": {
71
+ "type": "array",
72
+ "items": {"type": "string"},
73
+ "description": "Wagon slugs allowed without train coverage"
74
+ },
75
+ "features_orphaned": {
76
+ "type": "array",
77
+ "items": {"type": "string"},
78
+ "description": "Feature URNs allowed without wagon manifest reference"
79
+ },
80
+ "wmbts_without_acceptance": {
81
+ "type": "array",
82
+ "items": {"type": "string"},
83
+ "description": "WMBT URNs allowed without acceptance criteria"
84
+ },
85
+ "acceptance_without_tests": {
86
+ "type": "array",
87
+ "items": {"type": "string"},
88
+ "description": "Acceptance URNs allowed without test coverage"
89
+ },
90
+ "contracts_unreferenced": {
91
+ "type": "array",
92
+ "items": {"type": "string"},
93
+ "description": "Contract URNs allowed without produce/consume"
94
+ },
95
+ "telemetry_unreferenced": {
96
+ "type": "array",
97
+ "items": {"type": "string"},
98
+ "description": "Telemetry URNs allowed without references"
99
+ },
100
+ "features_without_implementation": {
101
+ "type": "array",
102
+ "items": {"type": "string"},
103
+ "description": "Feature URNs allowed without code implementation"
104
+ }
105
+ },
106
+ "additionalProperties": false
107
+ },
108
+ "thresholds": {
109
+ "type": "object",
110
+ "description": "Coverage threshold settings",
111
+ "properties": {
112
+ "min_acceptance_coverage": {
113
+ "type": "number",
114
+ "minimum": 0,
115
+ "maximum": 100,
116
+ "default": 80,
117
+ "description": "Minimum percentage of acceptances that must have tests"
118
+ }
119
+ },
120
+ "additionalProperties": false
121
+ }
122
+ },
123
+ "additionalProperties": false
61
124
  }
62
125
  },
63
126
  "required": ["version", "release"],
@@ -71,7 +71,7 @@ code:
71
71
  # Dev Servers
72
72
  dev_servers:
73
73
  backend:
74
- command: "cd python && python3 game.py"
74
+ command: "cd python && python3 app.py"
75
75
  url: "http://127.0.0.1:8000"
76
76
  swagger: "http://127.0.0.1:8000/docs"
77
77
  frontend:
@@ -0,0 +1,97 @@
1
+ """
2
+ ATDD Hierarchy Coverage Spec v0.1 Rollout Phase Controller.
3
+
4
+ Manages the phased rollout of coverage validation rules:
5
+ - Phase 1 (WARNINGS_ONLY): All validators emit warnings only
6
+ - Phase 2 (PLANNER_TESTER_ENFORCEMENT): Sections 2 + 3 validators strict
7
+ - Phase 3 (FULL_ENFORCEMENT): All validators (including Section 4) strict
8
+
9
+ Usage in validators:
10
+ from atdd.coach.utils.coverage_phase import CoveragePhase, should_enforce
11
+
12
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
13
+ assert condition, "Error message"
14
+ else:
15
+ if not condition:
16
+ emit_coverage_warning("COVERAGE-PLAN-2.1", "Warning message", CoveragePhase.PLANNER_TESTER_ENFORCEMENT)
17
+ """
18
+
19
+ from enum import IntEnum
20
+ from typing import Optional
21
+ import warnings
22
+
23
+
24
+ class CoveragePhase(IntEnum):
25
+ """
26
+ Rollout phases for ATDD Hierarchy Coverage Spec v0.1.
27
+
28
+ Phases are ordered by strictness level:
29
+ - WARNINGS_ONLY (1): All new validators emit warnings, no assertions
30
+ - PLANNER_TESTER_ENFORCEMENT (2): Planner (Section 2) + Tester (Section 3) strict
31
+ - FULL_ENFORCEMENT (3): All validators including Coder (Section 4) strict
32
+ """
33
+ WARNINGS_ONLY = 1
34
+ PLANNER_TESTER_ENFORCEMENT = 2
35
+ FULL_ENFORCEMENT = 3
36
+
37
+
38
+ # Current rollout phase - update this to advance through phases
39
+ CURRENT_PHASE = CoveragePhase.WARNINGS_ONLY
40
+
41
+
42
+ def should_enforce(validator_phase: CoveragePhase) -> bool:
43
+ """
44
+ Check if a validator should enforce strict mode.
45
+
46
+ Args:
47
+ validator_phase: The phase at which this validator becomes strict
48
+
49
+ Returns:
50
+ True if current phase >= validator_phase (should enforce)
51
+ False if current phase < validator_phase (should warn only)
52
+
53
+ Example:
54
+ # This validator becomes strict in Phase 2
55
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
56
+ assert wagon_in_train, "Wagon must be in at least one train"
57
+ else:
58
+ if not wagon_in_train:
59
+ emit_coverage_warning("COVERAGE-PLAN-2.1", "Wagon not in any train", CoveragePhase.PLANNER_TESTER_ENFORCEMENT)
60
+ """
61
+ return CURRENT_PHASE >= validator_phase
62
+
63
+
64
+ def get_current_phase() -> CoveragePhase:
65
+ """Get the current rollout phase."""
66
+ return CURRENT_PHASE
67
+
68
+
69
+ def get_phase_name(phase: Optional[CoveragePhase] = None) -> str:
70
+ """Get human-readable name for a phase."""
71
+ phase = phase or CURRENT_PHASE
72
+ return {
73
+ CoveragePhase.WARNINGS_ONLY: "Phase 1: Warnings Only",
74
+ CoveragePhase.PLANNER_TESTER_ENFORCEMENT: "Phase 2: Planner+Tester Enforcement",
75
+ CoveragePhase.FULL_ENFORCEMENT: "Phase 3: Full Enforcement",
76
+ }.get(phase, "Unknown Phase")
77
+
78
+
79
+ def emit_coverage_warning(
80
+ spec_id: str,
81
+ message: str,
82
+ validator_phase: CoveragePhase = CoveragePhase.PLANNER_TESTER_ENFORCEMENT
83
+ ) -> None:
84
+ """
85
+ Emit a coverage validation warning with phase context.
86
+
87
+ Args:
88
+ spec_id: The SPEC ID (e.g., "COVERAGE-PLAN-2.1")
89
+ message: The warning message
90
+ validator_phase: Phase when this becomes an error
91
+ """
92
+ phase_name = get_phase_name(validator_phase)
93
+ warnings.warn(
94
+ f"[{spec_id}] {message} (will become error in {phase_name})",
95
+ category=UserWarning,
96
+ stacklevel=3
97
+ )
@@ -445,3 +445,157 @@ def pytest_html_results_summary(prefix, summary, postfix):
445
445
  'against platform schemas and conventions.</p>'
446
446
  '</div>'
447
447
  ])
448
+
449
+
450
+ # ============================================================================
451
+ # COVERAGE VALIDATION FIXTURES (ATDD Hierarchy Coverage Spec v0.1)
452
+ # ============================================================================
453
+
454
+
455
+ @pytest.fixture(scope="module")
456
+ def coverage_exceptions(atdd_config: Dict[str, Any]) -> Dict[str, List[str]]:
457
+ """
458
+ Load coverage exception allow-lists from .atdd/config.yaml.
459
+
460
+ Returns:
461
+ Dict mapping exception type to list of allowed URNs/slugs
462
+ """
463
+ return atdd_config.get("coverage", {}).get("exceptions", {})
464
+
465
+
466
+ @pytest.fixture(scope="module")
467
+ def coverage_thresholds(atdd_config: Dict[str, Any]) -> Dict[str, Any]:
468
+ """
469
+ Load coverage threshold settings from .atdd/config.yaml.
470
+
471
+ Returns:
472
+ Dict with threshold settings (min_acceptance_coverage, etc.)
473
+ """
474
+ defaults = {"min_acceptance_coverage": 80}
475
+ thresholds = atdd_config.get("coverage", {}).get("thresholds", {})
476
+ return {**defaults, **thresholds}
477
+
478
+
479
+ @pytest.fixture(scope="module")
480
+ def feature_files() -> List[Tuple[Path, Dict[str, Any]]]:
481
+ """
482
+ Discover all feature files in plan/*/features/.
483
+
484
+ Returns:
485
+ List of (path, feature_data) tuples
486
+ """
487
+ import re
488
+ features = []
489
+ if not PLAN_DIR.exists():
490
+ return features
491
+
492
+ for wagon_dir in PLAN_DIR.iterdir():
493
+ if wagon_dir.is_dir() and not wagon_dir.name.startswith("_"):
494
+ features_dir = wagon_dir / "features"
495
+ if features_dir.exists():
496
+ for feature_file in features_dir.glob("*.yaml"):
497
+ try:
498
+ with open(feature_file) as f:
499
+ data = yaml.safe_load(f)
500
+ if data:
501
+ features.append((feature_file, data))
502
+ except Exception:
503
+ pass
504
+ return features
505
+
506
+
507
+ @pytest.fixture(scope="module")
508
+ def wmbt_files() -> List[Tuple[Path, Dict[str, Any]]]:
509
+ """
510
+ Discover all WMBT files in plan/*/.
511
+
512
+ WMBT files match pattern: [DLPCEMYRK]NNN.yaml (e.g., D001.yaml, L010.yaml)
513
+
514
+ Returns:
515
+ List of (path, wmbt_data) tuples
516
+ """
517
+ import re
518
+ wmbts = []
519
+ if not PLAN_DIR.exists():
520
+ return wmbts
521
+
522
+ wmbt_pattern = re.compile(r"^[DLPCEMYRK]\d{3}\.yaml$")
523
+
524
+ for wagon_dir in PLAN_DIR.iterdir():
525
+ if wagon_dir.is_dir() and not wagon_dir.name.startswith("_"):
526
+ for wmbt_file in wagon_dir.glob("*.yaml"):
527
+ if wmbt_pattern.match(wmbt_file.name):
528
+ try:
529
+ with open(wmbt_file) as f:
530
+ data = yaml.safe_load(f)
531
+ if data:
532
+ wmbts.append((wmbt_file, data))
533
+ except Exception:
534
+ pass
535
+ return wmbts
536
+
537
+
538
+ @pytest.fixture(scope="module")
539
+ def acceptance_urns_by_wagon(wmbt_files: List[Tuple[Path, Dict[str, Any]]]) -> Dict[str, List[str]]:
540
+ """
541
+ Extract all acceptance URNs grouped by wagon slug.
542
+
543
+ Returns:
544
+ Dict mapping wagon slug to list of acceptance URNs
545
+ """
546
+ by_wagon: Dict[str, List[str]] = {}
547
+
548
+ for path, wmbt_data in wmbt_files:
549
+ # Derive wagon slug from directory name (snake_case -> kebab-case)
550
+ wagon_slug = path.parent.name.replace("_", "-")
551
+
552
+ if wagon_slug not in by_wagon:
553
+ by_wagon[wagon_slug] = []
554
+
555
+ for acc in wmbt_data.get("acceptances", []):
556
+ if isinstance(acc, dict) and "identity" in acc:
557
+ urn = acc["identity"].get("urn", "")
558
+ if urn:
559
+ by_wagon[wagon_slug].append(urn)
560
+ elif isinstance(acc, str):
561
+ by_wagon[wagon_slug].append(acc)
562
+
563
+ return by_wagon
564
+
565
+
566
+ @pytest.fixture(scope="module")
567
+ def all_acceptance_urns(acceptance_urns_by_wagon: Dict[str, List[str]]) -> List[str]:
568
+ """
569
+ Get flat list of all acceptance URNs across all wagons.
570
+
571
+ Returns:
572
+ List of all acceptance URNs
573
+ """
574
+ all_urns = []
575
+ for urns in acceptance_urns_by_wagon.values():
576
+ all_urns.extend(urns)
577
+ return all_urns
578
+
579
+
580
+ @pytest.fixture(scope="module")
581
+ def wagon_to_train_mapping(train_files: List[Tuple[Path, Dict]]) -> Dict[str, List[str]]:
582
+ """
583
+ Build mapping of wagon slugs to train IDs that reference them.
584
+
585
+ Returns:
586
+ Dict mapping wagon slug to list of train IDs
587
+ """
588
+ mapping: Dict[str, List[str]] = {}
589
+
590
+ for train_path, train_data in train_files:
591
+ train_id = train_data.get("train_id", train_path.stem)
592
+ participants = train_data.get("participants", [])
593
+
594
+ for participant in participants:
595
+ if isinstance(participant, str) and participant.startswith("wagon:"):
596
+ wagon_slug = participant.replace("wagon:", "")
597
+ if wagon_slug not in mapping:
598
+ mapping[wagon_slug] = []
599
+ mapping[wagon_slug].append(train_id)
600
+
601
+ return mapping
@@ -281,7 +281,7 @@ backend:
281
281
 
282
282
  direct_adapter:
283
283
  pattern: "direct_*_client.py"
284
- use_when: "Wagons run in same process (monolith via game.py)"
284
+ use_when: "Wagons run in same process (monolith via app.py)"
285
285
  example: "DirectCommitStateClient reads from shared StateRepository"
286
286
  benefits:
287
287
  - "No network latency"
@@ -243,7 +243,7 @@ interaction:
243
243
 
244
244
  composition_roots:
245
245
  description: |
246
- Composition roots (composition.py, wagon.py, trains/runner.py, game.py) are entrypoints
246
+ Composition roots (composition.py, wagon.py, trains/runner.py, app.py) are entrypoints
247
247
  that wire dependencies. They are executed, never imported by other code.
248
248
 
249
249
  feature_composition:
@@ -332,15 +332,15 @@ interaction:
332
332
  # ========================================================================
333
333
  station_master_pattern:
334
334
  description: |
335
- When multiple wagons run in a single process (game.py), the Station Master
335
+ When multiple wagons run in a single process (app.py), the Station Master
336
336
  pattern enables shared dependency injection without HTTP self-calls.
337
337
 
338
- game.py creates shared singletons (StateRepository, EventBus, etc.) and
338
+ app.py creates shared singletons (StateRepository, EventBus, etc.) and
339
339
  passes them to wagon composition.py functions, which decide internally
340
340
  whether to use Direct adapters (monolith) or HTTP adapters (microservices).
341
341
 
342
342
  architecture: |
343
- game.py (Station Master / Thin Router)
343
+ app.py (Station Master / Thin Router)
344
344
 
345
345
  ├── Creates shared singletons:
346
346
  │ - StateRepository (commit-state data)
@@ -391,7 +391,7 @@ interaction:
391
391
  """Wire dependencies with optional SharedDependencies.
392
392
 
393
393
  Args:
394
- shared: SharedDependencies from game.py (monolith mode).
394
+ shared: SharedDependencies from app.py (monolith mode).
395
395
  When provided, uses Direct adapters for cross-wagon data.
396
396
  When None, uses Fake adapters for standalone testing.
397
397
  """
@@ -424,7 +424,7 @@ interaction:
424
424
  Same interface (implements Port), different implementation.
425
425
 
426
426
  station_master_responsibilities:
427
- game_py:
427
+ app_py:
428
428
  - "Create shared singletons (StateRepository, EventBus)"
429
429
  - "Pass shared deps to wagon composition.py"
430
430
  - "Include wagon routers in FastAPI app"
@@ -440,12 +440,12 @@ interaction:
440
440
  required:
441
441
  - "composition.py accepts optional shared dependency parameters"
442
442
  - "Direct adapters exist for cross-wagon data access"
443
- - "game.py calls composition.py, not duplicates wiring"
443
+ - "app.py calls composition.py, not duplicates wiring"
444
444
 
445
445
  forbidden:
446
- - "game.py creating use cases that composition.py should own"
446
+ - "app.py creating use cases that composition.py should own"
447
447
  - "HTTP clients calling localhost in production monolith"
448
- - "Duplicated wiring logic between game.py and composition.py"
448
+ - "Duplicated wiring logic between app.py and composition.py"
449
449
 
450
450
  forbidden_cross_wagon_imports:
451
451
  rule: "Code in wagon A MUST NOT import directly from wagon B"
@@ -0,0 +1,85 @@
1
+ version: "1.0"
2
+ name: "Coder Coverage Convention"
3
+ description: "Section 4 of ATDD Hierarchy Coverage Spec v0.1 - Ensures code implementation covers all features"
4
+
5
+ rules:
6
+ - id: "COVERAGE-CODE-4.1"
7
+ name: "Feature - Implementation Coverage"
8
+ description: "Every feature must have corresponding code implementation"
9
+ requirement: "Each feature's components must map to code paths"
10
+ exceptions: "features_without_implementation allow-list"
11
+ validator: "test_all_features_have_implementations"
12
+
13
+ - id: "COVERAGE-CODE-4.2"
14
+ name: "Implementation - Tests Coverage"
15
+ description: "Every implementation must have at least one linked test"
16
+ requirement: "Each implementation must have at least one linked test"
17
+ validator: "test_all_implementations_have_tests"
18
+
19
+ coverage_graph:
20
+ description: "The coder coverage graph ensures features are implemented and tested"
21
+ levels:
22
+ - name: "Feature"
23
+ implemented_by: "Code files in python/, supabase/, web/"
24
+ validators: ["COVERAGE-CODE-4.1"]
25
+
26
+ - name: "Implementation"
27
+ tested_by: "Test files referencing feature/component"
28
+ validators: ["COVERAGE-CODE-4.2"]
29
+
30
+ exception_handling:
31
+ description: "How exceptions are handled in coder coverage validation"
32
+ allow_lists:
33
+ features_without_implementation:
34
+ description: "Feature URNs that are allowed without code implementation"
35
+ use_case: "Planned features, design-only features, or features implemented externally"
36
+ config_path: "coverage.exceptions.features_without_implementation"
37
+
38
+ implementation_patterns:
39
+ description: "How features map to code implementations"
40
+
41
+ python:
42
+ location: "python/{wagon}/"
43
+ patterns:
44
+ - "use_case_{feature_slug}.py"
45
+ - "service_{feature_slug}.py"
46
+ - "{feature_slug}_handler.py"
47
+ example: "python/maintain_ux/use_case_provide_foundations.py"
48
+
49
+ typescript:
50
+ location: "supabase/functions/{wagon}/{feature}/"
51
+ patterns:
52
+ - "index.ts"
53
+ - "handler.ts"
54
+ - "{feature_slug}.ts"
55
+ example: "supabase/functions/maintain-ux/provide-foundations/index.ts"
56
+
57
+ web:
58
+ location: "web/src/features/{wagon}/{feature}/"
59
+ patterns:
60
+ - "index.tsx"
61
+ - "{feature_slug}.tsx"
62
+ - "components/"
63
+ example: "web/src/features/maintain-ux/provide-foundations/index.tsx"
64
+
65
+ test_patterns:
66
+ description: "How implementations map to tests"
67
+
68
+ python:
69
+ location: "python/{wagon}/"
70
+ filename: "test_*.py"
71
+ reference_method: "Import or reference to implementation module"
72
+
73
+ typescript:
74
+ location: "supabase/functions/{wagon}/{feature}/test/"
75
+ filename: "*.test.ts"
76
+ reference_method: "Import or reference to implementation"
77
+
78
+ web:
79
+ location: "web/tests/{wagon}/{feature}/"
80
+ filename: "*.test.tsx"
81
+ reference_method: "Import or reference to component"
82
+
83
+ rollout:
84
+ phase: "FULL_ENFORCEMENT"
85
+ description: "Section 4 validators become strict in Phase 3 (after planner+tester)"
@@ -540,25 +540,25 @@ validation:
540
540
  - "Simple error handling has TODO(REFACTOR) marker"
541
541
 
542
542
  unified_game_server:
543
- description: "python/game.py must aggregate all wagon presentation layers"
544
- file: "python/game.py"
543
+ description: "python/app.py must aggregate all wagon presentation layers"
544
+ file: "python/app.py"
545
545
  requirements:
546
- - "When new wagon adds FastAPI controller, game.py MUST be updated"
547
- - "game.py imports controller's app and includes via app.include_router()"
546
+ - "When new wagon adds FastAPI controller, app.py MUST be updated"
547
+ - "app.py imports controller's app and includes via app.include_router()"
548
548
  - "All wagon endpoints accessible via unified game server"
549
- - "Validator checks: all wagons with presentation are registered in game.py"
549
+ - "Validator checks: all wagons with presentation are registered in app.py"
550
550
 
551
551
  rationale: |
552
- game.py is the unified entry point for all backend services.
552
+ app.py is the unified entry point for all backend services.
553
553
  Without registration, wagon endpoints are inaccessible to game server/mobile app.
554
554
 
555
555
  pattern: |
556
- # python/game.py
556
+ # python/app.py
557
557
  from burn_timebank.track_remaining.src.presentation.controllers.remaining_controller import app as timebank_app
558
558
  app.include_router(timebank_app.router, prefix="/timebank")
559
559
 
560
560
  anti_pattern: |
561
- # ❌ WRONG: Wagon has FastAPI controller but not registered in game.py
561
+ # ❌ WRONG: Wagon has FastAPI controller but not registered in app.py
562
562
  # Result: Endpoints exist but unreachable from unified server
563
563
 
564
564
  usage: |
@@ -8,8 +8,8 @@ rationale: |
8
8
  defined in YAML specifications by coordinating wagons through contract-based communication.
9
9
 
10
10
  TRANSFORMATION (SESSION-12):
11
- - OLD: Trains in e2e/ (test-only), custom orchestration in game.py
12
- - NEW: Trains in python/trains/ (production), game.py becomes thin Station Master
11
+ - OLD: Trains in e2e/ (test-only), custom orchestration in app.py
12
+ - NEW: Trains in python/trains/ (production), app.py becomes thin Station Master
13
13
 
14
14
  BENEFITS:
15
15
  - Single source of truth (YAML defines both production AND tests)
@@ -21,7 +21,7 @@ cross_references:
21
21
  composition_hierarchy:
22
22
  - file: "refactor.convention.yaml"
23
23
  section: "composition_root.hierarchy"
24
- note: "Trains add new orchestration layer between game.py and wagon.py"
24
+ note: "Trains add new orchestration layer between app.py and wagon.py"
25
25
 
26
26
  boundaries:
27
27
  - file: "boundaries.convention.yaml"
@@ -37,14 +37,15 @@ cross_references:
37
37
  # ============================================================================
38
38
 
39
39
  composition_hierarchy:
40
- description: "Trains sit between application (game.py) and wagons (wagon.py)"
40
+ description: "Trains sit between application (app.py) and wagons (wagon.py)"
41
41
 
42
42
  levels:
43
43
  application:
44
- file: "python/game.py"
44
+ file: "python/app.py"
45
45
  role: "Station Master - thin router"
46
46
  responsibility: "Map user actions → train_ids, invoke TrainRunner"
47
47
  size: "< 50 lines of routing logic"
48
+ note: "app.py is the Station Master entrypoint"
48
49
 
49
50
  train:
50
51
  file: "python/trains/runner.py"
@@ -65,8 +66,8 @@ composition_hierarchy:
65
66
 
66
67
  dependency_flow:
67
68
  rule: "Dependencies flow downward only"
68
- chain: "game.py → trains.runner → wagon.py → composition.py → src/"
69
- forbidden: "NEVER: wagon imports from trains or game"
69
+ chain: "app.py → trains.runner → wagon.py → composition.py → src/"
70
+ forbidden: "NEVER: wagon imports from trains or app"
70
71
 
71
72
  # ============================================================================
72
73
  # TRAIN INFRASTRUCTURE
@@ -138,7 +139,7 @@ wagon_train_mode:
138
139
  multi_mode_pattern:
139
140
  description: "Wagons support multiple execution modes"
140
141
  cli: "Interactive demo (python3 wagon.py)"
141
- http: "API endpoints (via game.py)"
142
+ http: "API endpoints (via app.py)"
142
143
  train: "Production orchestration (via TrainRunner)"
143
144
 
144
145
  # ============================================================================
@@ -170,14 +171,14 @@ cargo_pattern:
170
171
  method: "JSON Schema Draft-07 validation"
171
172
 
172
173
  # ============================================================================
173
- # STATION MASTER PATTERN (game.py)
174
+ # STATION MASTER PATTERN (app.py)
174
175
  # ============================================================================
175
176
 
176
177
  station_master:
177
- description: "game.py becomes thin router that delegates to TrainRunner"
178
+ description: "app.py becomes thin router that delegates to TrainRunner"
178
179
 
179
180
  pattern: |
180
- # game.py - Station Master
181
+ # app.py - Station Master
181
182
  from trains.runner import TrainRunner
182
183
 
183
184
  JOURNEY_MAP = {
@@ -198,7 +199,7 @@ station_master:
198
199
  - "Translate HTTP ↔ artifacts"
199
200
  - "Handle errors"
200
201
 
201
- anti_pattern: "Business logic in game.py (belongs in trains/wagons)"
202
+ anti_pattern: "Business logic in app.py (belongs in trains/wagons)"
202
203
 
203
204
  # ============================================================================
204
205
  # TESTING
@@ -278,10 +279,10 @@ observability:
278
279
  # ============================================================================
279
280
 
280
281
  migration:
281
- step_1: "Identify user journeys in game.py"
282
+ step_1: "Identify user journeys in app.py"
282
283
  step_2: "Create train YAML (plan/_trains/{train_id}.yaml)"
283
284
  step_3: "Add run_train() to participating wagons"
284
- step_4: "Refactor game.py to Station Master pattern"
285
+ step_4: "Refactor app.py to Station Master pattern"
285
286
  step_5: "Update tests to use TrainRunner"
286
287
 
287
288
  # ============================================================================
@@ -0,0 +1,5 @@
1
+ """
2
+ Shared fixtures for coder tests.
3
+ """
4
+ # Import all shared fixtures from coach via absolute import
5
+ from atdd.coach.validators.shared_fixtures import *