atdd 0.6.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.
- atdd/coach/templates/ATDD.md +1 -1
- atdd/coder/conventions/backend.convention.yaml +1 -1
- atdd/coder/conventions/boundaries.convention.yaml +9 -9
- atdd/coder/conventions/presentation.convention.yaml +8 -8
- atdd/coder/conventions/train.convention.yaml +15 -14
- atdd/coder/validators/test_presentation_convention.py +11 -11
- atdd/coder/validators/test_station_master_pattern.py +16 -14
- atdd/coder/validators/test_train_infrastructure.py +22 -14
- atdd/coder/validators/test_wagon_boundaries.py +2 -2
- {atdd-0.6.0.dist-info → atdd-0.6.1.dist-info}/METADATA +1 -1
- {atdd-0.6.0.dist-info → atdd-0.6.1.dist-info}/RECORD +15 -15
- {atdd-0.6.0.dist-info → atdd-0.6.1.dist-info}/WHEEL +0 -0
- {atdd-0.6.0.dist-info → atdd-0.6.1.dist-info}/entry_points.txt +0 -0
- {atdd-0.6.0.dist-info → atdd-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {atdd-0.6.0.dist-info → atdd-0.6.1.dist-info}/top_level.txt +0 -0
atdd/coach/templates/ATDD.md
CHANGED
|
@@ -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
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
- "
|
|
443
|
+
- "app.py calls composition.py, not duplicates wiring"
|
|
444
444
|
|
|
445
445
|
forbidden:
|
|
446
|
-
- "
|
|
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
|
|
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"
|
|
@@ -540,25 +540,25 @@ validation:
|
|
|
540
540
|
- "Simple error handling has TODO(REFACTOR) marker"
|
|
541
541
|
|
|
542
542
|
unified_game_server:
|
|
543
|
-
description: "python/
|
|
544
|
-
file: "python/
|
|
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,
|
|
547
|
-
- "
|
|
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
|
|
549
|
+
- "Validator checks: all wagons with presentation are registered in app.py"
|
|
550
550
|
|
|
551
551
|
rationale: |
|
|
552
|
-
|
|
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/
|
|
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
|
|
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
|
|
12
|
-
- NEW: Trains in python/trains/ (production),
|
|
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
|
|
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 (
|
|
40
|
+
description: "Trains sit between application (app.py) and wagons (wagon.py)"
|
|
41
41
|
|
|
42
42
|
levels:
|
|
43
43
|
application:
|
|
44
|
-
file: "python/
|
|
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: "
|
|
69
|
-
forbidden: "NEVER: wagon imports from trains or
|
|
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
|
|
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 (
|
|
174
|
+
# STATION MASTER PATTERN (app.py)
|
|
174
175
|
# ============================================================================
|
|
175
176
|
|
|
176
177
|
station_master:
|
|
177
|
-
description: "
|
|
178
|
+
description: "app.py becomes thin router that delegates to TrainRunner"
|
|
178
179
|
|
|
179
180
|
pattern: |
|
|
180
|
-
#
|
|
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
|
|
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
|
|
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
|
|
285
|
+
step_4: "Refactor app.py to Station Master pattern"
|
|
285
286
|
step_5: "Update tests to use TrainRunner"
|
|
286
287
|
|
|
287
288
|
# ============================================================================
|
|
@@ -180,16 +180,16 @@ class PresentationValidator:
|
|
|
180
180
|
|
|
181
181
|
|
|
182
182
|
def validate_game_server_registration(self):
|
|
183
|
-
"""Validate python/
|
|
184
|
-
|
|
183
|
+
"""Validate python/app.py includes all wagons with presentation."""
|
|
184
|
+
server_file = self.python_root / "app.py"
|
|
185
185
|
|
|
186
|
-
if not
|
|
187
|
-
self.violations.append("❌ python/
|
|
186
|
+
if not server_file.exists():
|
|
187
|
+
self.violations.append("❌ python/app.py not found - unified server missing")
|
|
188
188
|
return
|
|
189
189
|
|
|
190
|
-
print(
|
|
190
|
+
print("\nValidating unified server: python/app.py")
|
|
191
191
|
|
|
192
|
-
content =
|
|
192
|
+
content = server_file.read_text()
|
|
193
193
|
|
|
194
194
|
# Find all wagons with FastAPI controllers
|
|
195
195
|
wagons_with_controllers = {}
|
|
@@ -209,7 +209,7 @@ class PresentationValidator:
|
|
|
209
209
|
if "fastapi" in controller_file.read_text().lower():
|
|
210
210
|
wagons_with_controllers[f"{wagon_dir.name}/{feature_dir.name}"] = controller_file
|
|
211
211
|
|
|
212
|
-
# Check if each controller is registered in
|
|
212
|
+
# Check if each controller is registered in the unified server file
|
|
213
213
|
for wagon_feature, controller_file in wagons_with_controllers.items():
|
|
214
214
|
wagon, feature = wagon_feature.split('/')
|
|
215
215
|
|
|
@@ -217,17 +217,17 @@ class PresentationValidator:
|
|
|
217
217
|
import_pattern = f"from {wagon}.{feature}.src.presentation.controllers"
|
|
218
218
|
if import_pattern not in content:
|
|
219
219
|
self.violations.append(
|
|
220
|
-
f"❌
|
|
220
|
+
f"❌ app.py missing import for {wagon}/{feature} controller"
|
|
221
221
|
)
|
|
222
222
|
|
|
223
223
|
# Check for include_router
|
|
224
224
|
if "include_router" in content and wagon not in content.lower():
|
|
225
225
|
self.violations.append(
|
|
226
|
-
f"⚠️
|
|
226
|
+
f"⚠️ app.py may not be registering {wagon}/{feature} routes"
|
|
227
227
|
)
|
|
228
228
|
|
|
229
229
|
if not self.violations:
|
|
230
|
-
print(f" ✓ All {len(wagons_with_controllers)} wagons registered in
|
|
230
|
+
print(f" ✓ All {len(wagons_with_controllers)} wagons registered in app.py")
|
|
231
231
|
|
|
232
232
|
|
|
233
233
|
def main():
|
|
@@ -236,7 +236,7 @@ def main():
|
|
|
236
236
|
|
|
237
237
|
parser = argparse.ArgumentParser(description="Validate presentation layer convention")
|
|
238
238
|
parser.add_argument("--check-game-server", action="store_true",
|
|
239
|
-
help="Validate python/
|
|
239
|
+
help="Validate python/app.py is up to date")
|
|
240
240
|
args = parser.parse_args()
|
|
241
241
|
|
|
242
242
|
python_root = REPO_ROOT / "python"
|
|
@@ -4,7 +4,7 @@ Station Master Pattern Validator
|
|
|
4
4
|
Validates that wagons follow the Station Master pattern for monolith composition:
|
|
5
5
|
1. composition.py accepts optional shared dependency parameters
|
|
6
6
|
2. Direct adapters exist for cross-wagon data access
|
|
7
|
-
3.
|
|
7
|
+
3. app.py delegates to composition.py instead of duplicating wiring
|
|
8
8
|
|
|
9
9
|
Convention: atdd/coder/conventions/boundaries.convention.yaml::station_master_pattern
|
|
10
10
|
"""
|
|
@@ -21,6 +21,9 @@ def get_python_dir() -> Path:
|
|
|
21
21
|
"""Get the python directory path in the consumer repo."""
|
|
22
22
|
return find_repo_root() / "python"
|
|
23
23
|
|
|
24
|
+
def resolve_server_file() -> Path:
|
|
25
|
+
"""Resolve station master entrypoint (app.py)."""
|
|
26
|
+
return get_python_dir() / "app.py"
|
|
24
27
|
|
|
25
28
|
def test_composition_accepts_shared_dependencies():
|
|
26
29
|
"""
|
|
@@ -161,12 +164,12 @@ def test_direct_adapters_exist_for_cross_wagon_clients():
|
|
|
161
164
|
|
|
162
165
|
def test_game_py_delegates_to_composition():
|
|
163
166
|
"""
|
|
164
|
-
Validate that
|
|
167
|
+
Validate that app.py delegates wiring to wagon composition.py files
|
|
165
168
|
instead of duplicating wiring logic.
|
|
166
169
|
|
|
167
170
|
Convention: boundaries.convention.yaml::station_master_pattern.station_master_responsibilities
|
|
168
171
|
|
|
169
|
-
Forbidden patterns in
|
|
172
|
+
Forbidden patterns in app.py:
|
|
170
173
|
- Creating use cases that composition.py should own
|
|
171
174
|
- Directly instantiating wagon clients without delegation
|
|
172
175
|
|
|
@@ -174,14 +177,13 @@ def test_game_py_delegates_to_composition():
|
|
|
174
177
|
- from wagon.composition import wire_api_dependencies
|
|
175
178
|
- wire_api_dependencies(state_repository=..., ...)
|
|
176
179
|
"""
|
|
177
|
-
|
|
178
|
-
game_py = python_dir / "game.py"
|
|
180
|
+
server_file = resolve_server_file()
|
|
179
181
|
|
|
180
|
-
if not
|
|
181
|
-
print("
|
|
182
|
+
if not server_file.exists():
|
|
183
|
+
print("app.py not found - skipping Station Master delegation check")
|
|
182
184
|
return
|
|
183
185
|
|
|
184
|
-
source =
|
|
186
|
+
source = server_file.read_text()
|
|
185
187
|
|
|
186
188
|
# Check for composition imports
|
|
187
189
|
imports_composition = "from play_match.orchestrate_match.composition import wire_api_dependencies" in source
|
|
@@ -190,7 +192,7 @@ def test_game_py_delegates_to_composition():
|
|
|
190
192
|
calls_wire_api = "wire_api_dependencies(" in source
|
|
191
193
|
|
|
192
194
|
# Check for forbidden patterns (duplicated wiring)
|
|
193
|
-
# These are patterns that should be in composition.py, not
|
|
195
|
+
# These are patterns that should be in composition.py, not app.py
|
|
194
196
|
forbidden_patterns = [
|
|
195
197
|
("PlayMatchUseCase(", "PlayMatchUseCase should be created in composition.py"),
|
|
196
198
|
("CommitStateClient(mode=", "CommitStateClient mode should be set in composition.py"),
|
|
@@ -204,7 +206,7 @@ def test_game_py_delegates_to_composition():
|
|
|
204
206
|
|
|
205
207
|
# Report results
|
|
206
208
|
print("\n" + "=" * 70)
|
|
207
|
-
print(" Station Master Pattern:
|
|
209
|
+
print(" Station Master Pattern: app.py Delegation")
|
|
208
210
|
print("=" * 70)
|
|
209
211
|
|
|
210
212
|
print(f"\nDelegation to composition.py:")
|
|
@@ -212,19 +214,19 @@ def test_game_py_delegates_to_composition():
|
|
|
212
214
|
print(f" {'✓' if calls_wire_api else '❌'} Calls wire_api_dependencies()")
|
|
213
215
|
|
|
214
216
|
if violations:
|
|
215
|
-
print(
|
|
217
|
+
print("\n❌ Violations found in app.py:")
|
|
216
218
|
for pattern, message in violations:
|
|
217
219
|
print(f" ❌ {message}")
|
|
218
220
|
print(f" Found: {pattern}")
|
|
219
221
|
|
|
220
222
|
# This is a real validation
|
|
221
223
|
assert imports_composition or not calls_wire_api, \
|
|
222
|
-
"
|
|
224
|
+
"app.py should import wire_api_dependencies from composition.py"
|
|
223
225
|
|
|
224
226
|
assert len(violations) == 0, \
|
|
225
|
-
f"
|
|
227
|
+
f"app.py has {len(violations)} Station Master pattern violations"
|
|
226
228
|
|
|
227
|
-
print("\n✓
|
|
229
|
+
print("\n✓ app.py follows Station Master pattern")
|
|
228
230
|
|
|
229
231
|
|
|
230
232
|
def main():
|
|
@@ -11,7 +11,7 @@ Enforces:
|
|
|
11
11
|
- Wagons implement run_train() for train mode
|
|
12
12
|
- Contract validator is real (not mock)
|
|
13
13
|
- E2E tests use production TrainRunner
|
|
14
|
-
- Station Master pattern in
|
|
14
|
+
- Station Master pattern in app.py
|
|
15
15
|
|
|
16
16
|
Train First-Class Spec v0.6 additions:
|
|
17
17
|
- SPEC-TRAIN-VAL-0031: Backend runner paths
|
|
@@ -45,7 +45,7 @@ from atdd.coach.utils.config import get_train_config
|
|
|
45
45
|
REPO_ROOT = find_repo_root()
|
|
46
46
|
TRAINS_DIR = REPO_ROOT / "python" / "trains"
|
|
47
47
|
WAGONS_DIR = REPO_ROOT / "python"
|
|
48
|
-
|
|
48
|
+
APP_PY = REPO_ROOT / "python" / "app.py"
|
|
49
49
|
E2E_CONFTEST = REPO_ROOT / "e2e" / "conftest.py"
|
|
50
50
|
CONTRACT_VALIDATOR = REPO_ROOT / "e2e" / "shared" / "fixtures" / "contract_validator.py"
|
|
51
51
|
|
|
@@ -110,6 +110,11 @@ def extract_imports_from_file(file_path: Path) -> Set[str]:
|
|
|
110
110
|
return imports
|
|
111
111
|
|
|
112
112
|
|
|
113
|
+
def resolve_server_file() -> Path:
|
|
114
|
+
"""Resolve station master entrypoint (app.py)."""
|
|
115
|
+
return APP_PY
|
|
116
|
+
|
|
117
|
+
|
|
113
118
|
# ============================================================================
|
|
114
119
|
# TRAIN INFRASTRUCTURE TESTS
|
|
115
120
|
# ============================================================================
|
|
@@ -247,45 +252,48 @@ def test_wagons_implement_run_train():
|
|
|
247
252
|
|
|
248
253
|
|
|
249
254
|
# ============================================================================
|
|
250
|
-
# STATION MASTER TESTS (
|
|
255
|
+
# STATION MASTER TESTS (app.py)
|
|
251
256
|
# ============================================================================
|
|
252
257
|
|
|
253
258
|
def test_game_py_imports_train_runner():
|
|
254
|
-
"""
|
|
255
|
-
|
|
259
|
+
"""app.py must import TrainRunner (Station Master pattern)."""
|
|
260
|
+
server_file = resolve_server_file()
|
|
261
|
+
assert server_file.exists(), f"app.py not found: {server_file}"
|
|
256
262
|
|
|
257
|
-
imports = extract_imports_from_file(
|
|
263
|
+
imports = extract_imports_from_file(server_file)
|
|
258
264
|
|
|
259
265
|
has_train_import = any("trains.runner import TrainRunner" in imp for imp in imports)
|
|
260
266
|
|
|
261
267
|
assert has_train_import, (
|
|
262
|
-
"
|
|
268
|
+
"app.py must import TrainRunner\n"
|
|
263
269
|
"Expected: from trains.runner import TrainRunner\n"
|
|
264
270
|
"See: atdd/coder/conventions/train.convention.yaml::station_master"
|
|
265
271
|
)
|
|
266
272
|
|
|
267
273
|
|
|
268
274
|
def test_game_py_has_journey_map():
|
|
269
|
-
"""
|
|
270
|
-
|
|
275
|
+
"""app.py must have JOURNEY_MAP routing actions to trains."""
|
|
276
|
+
server_file = resolve_server_file()
|
|
277
|
+
with open(server_file, 'r', encoding='utf-8') as f:
|
|
271
278
|
content = f.read()
|
|
272
279
|
|
|
273
280
|
assert "JOURNEY_MAP" in content, (
|
|
274
|
-
"
|
|
281
|
+
"app.py must define JOURNEY_MAP dictionary\n"
|
|
275
282
|
"Expected: JOURNEY_MAP = {'action': 'train_id', ...}\n"
|
|
276
283
|
"See: atdd/coder/conventions/train.convention.yaml::station_master"
|
|
277
284
|
)
|
|
278
285
|
|
|
279
286
|
|
|
280
287
|
def test_game_py_has_train_execution_endpoint():
|
|
281
|
-
"""
|
|
282
|
-
|
|
288
|
+
"""app.py must have /trains/execute endpoint."""
|
|
289
|
+
server_file = resolve_server_file()
|
|
290
|
+
with open(server_file, 'r', encoding='utf-8') as f:
|
|
283
291
|
content = f.read()
|
|
284
292
|
|
|
285
293
|
has_endpoint = '"/trains/execute"' in content or "'/trains/execute'" in content
|
|
286
294
|
|
|
287
295
|
assert has_endpoint, (
|
|
288
|
-
"
|
|
296
|
+
"app.py must have /trains/execute endpoint\n"
|
|
289
297
|
"Expected: @app.post('/trains/execute')\n"
|
|
290
298
|
"See: atdd/coder/conventions/train.convention.yaml::station_master"
|
|
291
299
|
)
|
|
@@ -407,7 +415,7 @@ def test_train_convention_documents_key_patterns():
|
|
|
407
415
|
- composition_hierarchy (with train level)
|
|
408
416
|
- wagon_train_mode (run_train signature)
|
|
409
417
|
- cargo_pattern (artifact flow)
|
|
410
|
-
- station_master (
|
|
418
|
+
- station_master (app.py pattern)
|
|
411
419
|
- testing_pattern (E2E tests)
|
|
412
420
|
"""
|
|
413
421
|
with open(TRAIN_CONVENTION, 'r', encoding='utf-8') as f:
|
|
@@ -78,8 +78,8 @@ def find_implementation_files() -> List[Path]:
|
|
|
78
78
|
# Skip __pycache__
|
|
79
79
|
if '__pycache__' in str(py_file):
|
|
80
80
|
continue
|
|
81
|
-
# Skip wagon.py, composition.py, and
|
|
82
|
-
if py_file.name in ['wagon.py', 'composition.py', '
|
|
81
|
+
# Skip wagon.py, composition.py, and app entrypoint (wagon/app-level orchestration)
|
|
82
|
+
if py_file.name in ['wagon.py', 'composition.py', 'app.py']:
|
|
83
83
|
continue
|
|
84
84
|
# Skip shared/ directory (theme/train-level orchestration - can import across wagons)
|
|
85
85
|
if '/shared/' in str(py_file):
|
|
@@ -27,7 +27,7 @@ atdd/coach/overlays/__init__.py,sha256=2lMiMSgfLJ3YHLpbzNI5B88AdQxiMEwjIfsWWb8t3
|
|
|
27
27
|
atdd/coach/overlays/claude.md,sha256=33mhpqhmsRhCtdWlU7cMXAJDsaVra9uBBK8URV8OtQA,101
|
|
28
28
|
atdd/coach/schemas/config.schema.json,sha256=XcqpISpV-57Kk7HdBXz_4K0jqj_SdKgBHy9VyVpNRD8,4191
|
|
29
29
|
atdd/coach/schemas/manifest.schema.json,sha256=WO13-YF_FgH1awh96khCtk-112b6XSC24anlY3B7GjY,2885
|
|
30
|
-
atdd/coach/templates/ATDD.md,sha256=
|
|
30
|
+
atdd/coach/templates/ATDD.md,sha256=h_oPpKLX7nuafC0VAoKCnkM2-kGQeksR34QWlGyfMwU,13236
|
|
31
31
|
atdd/coach/templates/SESSION-TEMPLATE.md,sha256=cGT_0x5KLbPHOCiuM8evLGpWKIlR-aggqxiBtbjSJoo,9478
|
|
32
32
|
atdd/coach/utils/__init__.py,sha256=7Jbo-heJEKSAn6I0s35z_2S4R8qGZ48PL6a2IntcNYg,148
|
|
33
33
|
atdd/coach/utils/config.py,sha256=6XXaaeVfjTrJwdaR0IZ6Kf1-1ZHhaCVLO5pNx_A2el4,3320
|
|
@@ -48,8 +48,8 @@ atdd/coach/validators/test_update_feature_paths.py,sha256=zOKVDgEIpncSJwDh_shyyo
|
|
|
48
48
|
atdd/coach/validators/test_validate_contract_consumers.py,sha256=b01yam_GwAERF6YaFmUV6Bd7SNMWQkUKBfNVvplbEcU,12613
|
|
49
49
|
atdd/coder/__init__.py,sha256=Rmi5S7Pzx7qsRe5MC66GduNGmkssWWnElkVvvNHFDgU,45
|
|
50
50
|
atdd/coder/conventions/adapter.recipe.yaml,sha256=Ss9OJJ-FEP8TU_D-N6X_1nO-Karb-Cg5OopL9_gLjMI,3039
|
|
51
|
-
atdd/coder/conventions/backend.convention.yaml,sha256=
|
|
52
|
-
atdd/coder/conventions/boundaries.convention.yaml,sha256=
|
|
51
|
+
atdd/coder/conventions/backend.convention.yaml,sha256=OJkBZUPaGFfKmO8UPslucbA6RZtsctvoywfMLFw5irg,16599
|
|
52
|
+
atdd/coder/conventions/boundaries.convention.yaml,sha256=uMahpzwNru8KuGDllXrajD5VQ5f62GPsz0nMsfgaAQw,25504
|
|
53
53
|
atdd/coder/conventions/commons.convention.yaml,sha256=zwfO4dYNrFjy4MLZ9RLK3Yd7gotTWYdFfXMPp3EfU0A,17309
|
|
54
54
|
atdd/coder/conventions/complexity.recipe.yaml,sha256=fjVnsb0kGGstTDKTaeYO_1XJ3408wE0lheAxf02Hm3s,3636
|
|
55
55
|
atdd/coder/conventions/component-naming.convention.yaml,sha256=w1LKJM7lhmpFqBCpRAvx03Z_Ugujd3P6rFfmp5xbhg0,7444
|
|
@@ -59,11 +59,11 @@ atdd/coder/conventions/design.recipe.yaml,sha256=LP2S504JpfzwoL3jPdSmYKMmg-_Bzlo
|
|
|
59
59
|
atdd/coder/conventions/dto.convention.yaml,sha256=aawWJVwoXU5yV73-rpHDSYtYMfm0XSWRRMujGCQrtYY,23700
|
|
60
60
|
atdd/coder/conventions/frontend.convention.yaml,sha256=2qBtLbqo1SrM17mXMlbZg4xlNAlq0_Kj2n18XphsQl8,19748
|
|
61
61
|
atdd/coder/conventions/green.convention.yaml,sha256=jq-Y5yHizf8o6NSJMD-9BPGrAv-otXRRjHikSGqn3aI,39091
|
|
62
|
-
atdd/coder/conventions/presentation.convention.yaml,sha256=
|
|
62
|
+
atdd/coder/conventions/presentation.convention.yaml,sha256=P8_J6Bh58AkeoGfsG91KPBO7pXJOvaYeCxp-JleX2Tw,21885
|
|
63
63
|
atdd/coder/conventions/refactor.convention.yaml,sha256=kpETeWqZRlahV55c9bEf1bLOokL_DDR0jY4mffKfY_c,19292
|
|
64
64
|
atdd/coder/conventions/technology.convention.yaml,sha256=OU6_XArKTKtLoI9c67GzAM3tjWj7Qx1qrW7uQgIXvVY,13826
|
|
65
65
|
atdd/coder/conventions/thinness.recipe.yaml,sha256=4FZplEJQA93qsvpJ4QPCn91duWtTL_V_CsqSgIFfBSo,2664
|
|
66
|
-
atdd/coder/conventions/train.convention.yaml,sha256=
|
|
66
|
+
atdd/coder/conventions/train.convention.yaml,sha256=saHeqQVzKkIjD1XxjTXApv5tGsQ_CPXFtsU8qbsvaag,11829
|
|
67
67
|
atdd/coder/conventions/verification.protocol.yaml,sha256=tYOId2A8xKoTqjcrKN1RNUK20_f0_e9GeVm_GBKrXMQ,1523
|
|
68
68
|
atdd/coder/conventions/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
69
|
atdd/coder/conventions/tests/test_adapter_recipe.py,sha256=293CU9rj7z6bj_1Wh02VhVCslaHfK-KeMJFgKgK7cvM,9370
|
|
@@ -87,15 +87,15 @@ atdd/coder/validators/test_hierarchy_coverage.py,sha256=1JuaiO4k-v3_rL5x4-MPsKaj
|
|
|
87
87
|
atdd/coder/validators/test_import_boundaries.py,sha256=3kzVMKIwZU9FcS0YLU8gsuHSDRlRr2rU3UPgoraV2FU,11811
|
|
88
88
|
atdd/coder/validators/test_init_file_urns.py,sha256=uDJ2MgfJNFcjzoIKItn73n9V08m3ZBkt1SAZgHWdXPs,17914
|
|
89
89
|
atdd/coder/validators/test_preact_layer_boundaries.py,sha256=rabXY9gif3b8QH9_Kz5Pu6FZSvDpfQKDm_SKjdpLD44,7766
|
|
90
|
-
atdd/coder/validators/test_presentation_convention.py,sha256=
|
|
90
|
+
atdd/coder/validators/test_presentation_convention.py,sha256=LKmiMStrR6uqcyYOyBr08EHyeGHHiN2UvBpO1EgUzZM,10040
|
|
91
91
|
atdd/coder/validators/test_python_architecture.py,sha256=USnSujKVu7_BC2ij-pTSHyiFi4iBMBemIPR7oXYQ3B4,25243
|
|
92
92
|
atdd/coder/validators/test_quality_metrics.py,sha256=vLWPfL0x9sfCvKXO4uECW61RnJTsjbmuEUjCsBcUmuA,12551
|
|
93
|
-
atdd/coder/validators/test_station_master_pattern.py,sha256=
|
|
94
|
-
atdd/coder/validators/test_train_infrastructure.py,sha256=
|
|
93
|
+
atdd/coder/validators/test_station_master_pattern.py,sha256=R0VajDIabKQtXKFCZPzWaY-euTcMLr0ECIim-s1_zX8,9374
|
|
94
|
+
atdd/coder/validators/test_train_infrastructure.py,sha256=_91k0d1Q7RkQZb-2N8fD_eRXe-oMMQGhQ_u_41ytHlU,24661
|
|
95
95
|
atdd/coder/validators/test_train_urns.py,sha256=TvSNHbvG8SAtAPaS2K8mMgzE8GbJtrTaF4iEgkJEIIk,10099
|
|
96
96
|
atdd/coder/validators/test_typescript_architecture.py,sha256=gi76IwlqX_MpWsr8CXWH7v16KAoud-1OY92iuN-0BpU,22176
|
|
97
97
|
atdd/coder/validators/test_usecase_structure.py,sha256=nAeqmLWRCstXT8fDuxqk6iVcH4TLHjTvlaGDpanD9sM,14077
|
|
98
|
-
atdd/coder/validators/test_wagon_boundaries.py,sha256=
|
|
98
|
+
atdd/coder/validators/test_wagon_boundaries.py,sha256=lifrhazbW2fJOwgU0vnNCjXZGfknUh6pa7err2TYwyY,20696
|
|
99
99
|
atdd/planner/__init__.py,sha256=ZXzKPNqP_JgEeFnwwUAKfIwz2e1i2gwrmz1SyC65sl4,47
|
|
100
100
|
atdd/planner/conventions/acceptance.convention.yaml,sha256=q_1hm-uJ2Sbh6VqGHQ543L9NTnOuS2UGWT-n9uAdfmM,21918
|
|
101
101
|
atdd/planner/conventions/appendix.convention.yaml,sha256=wuv9mnSZ-L1vUp2xx0OmyOsYurApitsmO_MilLVGcOc,9394
|
|
@@ -196,9 +196,9 @@ atdd/tester/validators/test_train_frontend_e2e.py,sha256=fpfUwTbAWzuqxbVKoaFw-ab
|
|
|
196
196
|
atdd/tester/validators/test_train_frontend_python.py,sha256=KK2U3oNFWLyBK7YHC0fU7shR05k93gVcO762AI8Q3pw,9018
|
|
197
197
|
atdd/tester/validators/test_typescript_test_naming.py,sha256=E-TyGv_GVlTfsbyuxrtv9sOWSZS_QcpH6rrJFbWoeeU,11280
|
|
198
198
|
atdd/tester/validators/test_typescript_test_structure.py,sha256=eV89SD1RaKtchBZupqhnJmaruoROosf3LwB4Fwe4UJI,2612
|
|
199
|
-
atdd-0.6.
|
|
200
|
-
atdd-0.6.
|
|
201
|
-
atdd-0.6.
|
|
202
|
-
atdd-0.6.
|
|
203
|
-
atdd-0.6.
|
|
204
|
-
atdd-0.6.
|
|
199
|
+
atdd-0.6.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
200
|
+
atdd-0.6.1.dist-info/METADATA,sha256=tQJjLbry8VRfv7IXzkaChFTwEM_yoV9QZjEa17TEb5A,8716
|
|
201
|
+
atdd-0.6.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
202
|
+
atdd-0.6.1.dist-info/entry_points.txt,sha256=-C3yrA1WQQfN3iuGmSzPapA5cKVBEYU5Q1HUffSJTbY,38
|
|
203
|
+
atdd-0.6.1.dist-info/top_level.txt,sha256=VKkf6Uiyrm4RS6ULCGM-v8AzYN8K2yg8SMqwJLoO-xs,5
|
|
204
|
+
atdd-0.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|