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,587 @@
|
|
|
1
|
+
version: "1.0"
|
|
2
|
+
name: "Presentation Convention"
|
|
3
|
+
description: "Presentation layer implementation patterns across Python, TypeScript, and Dart"
|
|
4
|
+
|
|
5
|
+
purpose: |
|
|
6
|
+
Defines how to implement presentation layer (HTTP REST, CLI, GraphQL) across runtimes.
|
|
7
|
+
|
|
8
|
+
Presentation layer translates external protocols (HTTP, CLI args) to domain operations
|
|
9
|
+
and formats domain artifacts back to external representations (JSON responses, terminal output).
|
|
10
|
+
|
|
11
|
+
Key principle: Presentation is THIN - all business logic stays in domain/application layers.
|
|
12
|
+
|
|
13
|
+
relationship:
|
|
14
|
+
component_types: "backend.convention.yaml (defines controller/route/serializer types)"
|
|
15
|
+
phase_guidance: "green.convention.yaml (when to create, GREEN simplifications)"
|
|
16
|
+
contract_alignment: "dto.convention.yaml (DTOs as API response models)"
|
|
17
|
+
architecture: "design.convention.yaml (dependency flow: presentation → application → domain)"
|
|
18
|
+
|
|
19
|
+
architecture:
|
|
20
|
+
layers:
|
|
21
|
+
presentation:
|
|
22
|
+
what: "External interface adapters (HTTP, CLI, GraphQL)"
|
|
23
|
+
location: "{wagon}/{feature}/src/presentation/"
|
|
24
|
+
authority: "presentation.convention.yaml (this file)"
|
|
25
|
+
owner: "coder agent"
|
|
26
|
+
|
|
27
|
+
application:
|
|
28
|
+
what: "Use cases orchestrating domain operations"
|
|
29
|
+
location: "{wagon}/{feature}/src/application/"
|
|
30
|
+
note: "Presentation calls application use cases, never domain directly"
|
|
31
|
+
|
|
32
|
+
contract_dto:
|
|
33
|
+
what: "Response models aligned with contract schemas"
|
|
34
|
+
location: "python/contracts/, dart/lib/contracts/"
|
|
35
|
+
note: "Pydantic/Freezed models matching JSON schemas"
|
|
36
|
+
|
|
37
|
+
flow: |
|
|
38
|
+
HTTP Request → Controller → Use Case → Domain
|
|
39
|
+
↓
|
|
40
|
+
JSON Response ← Response Model ← Artifact ← Domain
|
|
41
|
+
|
|
42
|
+
# ============================================================================
|
|
43
|
+
# HTTP REST API PATTERNS
|
|
44
|
+
# ============================================================================
|
|
45
|
+
|
|
46
|
+
http_rest_api:
|
|
47
|
+
description: "REST API implementation patterns across runtimes"
|
|
48
|
+
|
|
49
|
+
when_to_use:
|
|
50
|
+
- "Wagon produces artifacts (100% of wagons in contract-driven architecture)"
|
|
51
|
+
- "Artifact has contract schema that needs validation"
|
|
52
|
+
- "Manual testing via Swagger UI (faster than writing pytest)"
|
|
53
|
+
- "Game server/mobile app needs HTTP endpoint to consume artifact"
|
|
54
|
+
- "Cross-team testing without Python/runtime setup"
|
|
55
|
+
- "Debugging requires inspecting artifact JSON in browser"
|
|
56
|
+
|
|
57
|
+
rationale: |
|
|
58
|
+
We are CONTRACT-DRIVEN. Every wagon produces artifacts with schemas.
|
|
59
|
+
|
|
60
|
+
Pattern: Wagon → Domain → Artifact → Presentation (HTTP) → JSON Response
|
|
61
|
+
|
|
62
|
+
Benefits:
|
|
63
|
+
- Swagger UI validates contract alignment automatically
|
|
64
|
+
- Pydantic models enforce schema at runtime
|
|
65
|
+
- Developers test via browser, not pytest
|
|
66
|
+
- Game server integrates via REST, not Python imports
|
|
67
|
+
- QA tests without local Python setup
|
|
68
|
+
|
|
69
|
+
when_not_to_use:
|
|
70
|
+
- "Wagon produces no artifacts (0% of current wagons)"
|
|
71
|
+
- "Pure library code with no outputs (e.g., shared crypto utils)"
|
|
72
|
+
|
|
73
|
+
# --------------------------------------------------------------------------
|
|
74
|
+
# PYTHON FASTAPI
|
|
75
|
+
# --------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
python_fastapi:
|
|
78
|
+
version_support: "FastAPI 0.100+, Pydantic 2.0+, uvicorn"
|
|
79
|
+
|
|
80
|
+
file_structure:
|
|
81
|
+
controller: "python/{wagon}/{feature}/src/presentation/controllers/{feature}_controller.py"
|
|
82
|
+
response_models: "Pydantic models in same controller file (GREEN) or separate models.py (REFACTOR)"
|
|
83
|
+
urn_marker: "# urn: component:{wagon}:{feature}.{Feature}Controller.backend.presentation"
|
|
84
|
+
|
|
85
|
+
pattern:
|
|
86
|
+
description: "FastAPI app with Pydantic models aligned to contract schemas"
|
|
87
|
+
|
|
88
|
+
minimal_example: |
|
|
89
|
+
# urn: component:burn-timebank:track-remaining.RemainingController.backend.presentation
|
|
90
|
+
# Runtime: python
|
|
91
|
+
# Purpose: HTTP REST endpoint for timebank remaining
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
RemainingController - FastAPI REST endpoint.
|
|
95
|
+
|
|
96
|
+
Contract: mechanic:timebank.remaining
|
|
97
|
+
"""
|
|
98
|
+
from fastapi import FastAPI, Query
|
|
99
|
+
from pydantic import BaseModel, Field
|
|
100
|
+
from typing import Optional
|
|
101
|
+
|
|
102
|
+
# Import domain/application layers using QUALIFIED imports
|
|
103
|
+
import sys
|
|
104
|
+
from pathlib import Path
|
|
105
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent.parent))
|
|
106
|
+
|
|
107
|
+
from burn_timebank.track_remaining.src.domain.entities.timebank import Timebank
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Pydantic response model matching contract schema
|
|
111
|
+
class TimebankRemainingResponse(BaseModel):
|
|
112
|
+
"""
|
|
113
|
+
Response model for mechanic:timebank.remaining artifact.
|
|
114
|
+
|
|
115
|
+
Contract: contracts/mechanic/timebank/remaining.schema.json
|
|
116
|
+
"""
|
|
117
|
+
remaining_seconds: float = Field(
|
|
118
|
+
...,
|
|
119
|
+
description="Time remaining in seconds",
|
|
120
|
+
example=180.0
|
|
121
|
+
)
|
|
122
|
+
is_low: bool = Field(
|
|
123
|
+
...,
|
|
124
|
+
description="Whether time is below warning threshold",
|
|
125
|
+
example=False
|
|
126
|
+
)
|
|
127
|
+
preset: Optional[str] = Field(
|
|
128
|
+
None,
|
|
129
|
+
description="Time control preset",
|
|
130
|
+
example="blitz"
|
|
131
|
+
)
|
|
132
|
+
artifact_name: str = Field(
|
|
133
|
+
default="mechanic:timebank.remaining",
|
|
134
|
+
description="Artifact identifier"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# FastAPI application
|
|
139
|
+
app = FastAPI(
|
|
140
|
+
title="Burn Timebank API",
|
|
141
|
+
description="Track remaining time with low-time warnings",
|
|
142
|
+
version="0.1.0",
|
|
143
|
+
docs_url="/docs",
|
|
144
|
+
redoc_url="/redoc",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# GREEN: Simple global state
|
|
149
|
+
# TODO(REFACTOR): Use Redis/database for state management
|
|
150
|
+
_timebank_instance: Optional[Timebank] = None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@app.get(
|
|
154
|
+
"/timebank/remaining",
|
|
155
|
+
response_model=TimebankRemainingResponse,
|
|
156
|
+
summary="Get remaining timebank",
|
|
157
|
+
tags=["timebank"],
|
|
158
|
+
)
|
|
159
|
+
def get_remaining(
|
|
160
|
+
initial_seconds: Optional[float] = Query(None, example=180.0)
|
|
161
|
+
) -> TimebankRemainingResponse:
|
|
162
|
+
"""
|
|
163
|
+
Get current timebank state.
|
|
164
|
+
|
|
165
|
+
Contract: mechanic:timebank.remaining
|
|
166
|
+
"""
|
|
167
|
+
global _timebank_instance
|
|
168
|
+
|
|
169
|
+
# GREEN: Initialize if needed
|
|
170
|
+
if _timebank_instance is None or initial_seconds is not None:
|
|
171
|
+
_timebank_instance = Timebank(
|
|
172
|
+
initial_seconds=initial_seconds or 180.0,
|
|
173
|
+
preset="blitz"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Call domain layer
|
|
177
|
+
artifact = _timebank_instance.emit_remaining()
|
|
178
|
+
|
|
179
|
+
# Map domain artifact to response model
|
|
180
|
+
return TimebankRemainingResponse(
|
|
181
|
+
remaining_seconds=artifact.remaining_seconds,
|
|
182
|
+
is_low=artifact.is_low,
|
|
183
|
+
preset=artifact.preset,
|
|
184
|
+
artifact_name=artifact.artifact_name
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@app.post(
|
|
189
|
+
"/timebank/tick",
|
|
190
|
+
response_model=TimebankRemainingResponse,
|
|
191
|
+
summary="Decrement timebank",
|
|
192
|
+
tags=["timebank"],
|
|
193
|
+
)
|
|
194
|
+
def tick_timebank(
|
|
195
|
+
elapsed_seconds: float = Query(..., example=10.0)
|
|
196
|
+
) -> TimebankRemainingResponse:
|
|
197
|
+
"""Simulate time passage."""
|
|
198
|
+
global _timebank_instance
|
|
199
|
+
|
|
200
|
+
if _timebank_instance is None:
|
|
201
|
+
_timebank_instance = Timebank(180.0, "blitz")
|
|
202
|
+
|
|
203
|
+
# Call domain mutation
|
|
204
|
+
_timebank_instance.tick(elapsed_seconds=elapsed_seconds)
|
|
205
|
+
|
|
206
|
+
# Return updated state
|
|
207
|
+
artifact = _timebank_instance.emit_remaining()
|
|
208
|
+
return TimebankRemainingResponse(
|
|
209
|
+
remaining_seconds=artifact.remaining_seconds,
|
|
210
|
+
is_low=artifact.is_low,
|
|
211
|
+
preset=artifact.preset,
|
|
212
|
+
artifact_name=artifact.artifact_name
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
composition_integration:
|
|
216
|
+
description: "Dual-mode composition.py supporting CLI and HTTP"
|
|
217
|
+
|
|
218
|
+
pattern: |
|
|
219
|
+
# composition.py supports both modes
|
|
220
|
+
def main(mode: str = "cli"):
|
|
221
|
+
"""
|
|
222
|
+
Bootstrap application in CLI or HTTP mode.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
mode: "cli" for terminal demo, "http" for FastAPI server
|
|
226
|
+
"""
|
|
227
|
+
# Wire dependencies
|
|
228
|
+
timebank = Timebank(initial_seconds=180.0, preset="blitz")
|
|
229
|
+
|
|
230
|
+
if mode == "cli":
|
|
231
|
+
# CLI mode: Terminal demo
|
|
232
|
+
print("📊 Demo: Simulating time passage...")
|
|
233
|
+
for i in range(3):
|
|
234
|
+
timebank.tick(elapsed_seconds=10.0)
|
|
235
|
+
artifact = timebank.emit_remaining()
|
|
236
|
+
print(f"Tick {i+1}: {artifact.remaining_seconds}s")
|
|
237
|
+
|
|
238
|
+
return timebank
|
|
239
|
+
|
|
240
|
+
elif mode == "http":
|
|
241
|
+
# HTTP mode: FastAPI server
|
|
242
|
+
from src.presentation.controllers.feature_controller import app, _set_timebank
|
|
243
|
+
import uvicorn
|
|
244
|
+
|
|
245
|
+
# Initialize app with wired dependencies
|
|
246
|
+
_set_timebank(timebank)
|
|
247
|
+
|
|
248
|
+
print("🚀 Starting FastAPI server...")
|
|
249
|
+
print("📖 Swagger UI: http://127.0.0.1:8000/docs")
|
|
250
|
+
print("🔗 API: http://127.0.0.1:8000/resource")
|
|
251
|
+
|
|
252
|
+
# Run server
|
|
253
|
+
uvicorn.run(app, host="127.0.0.1", port=8000, reload=False)
|
|
254
|
+
return app
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
import argparse
|
|
258
|
+
parser = argparse.ArgumentParser()
|
|
259
|
+
parser.add_argument("--mode", choices=["cli", "http"], default="cli")
|
|
260
|
+
args = parser.parse_args()
|
|
261
|
+
main(mode=args.mode)
|
|
262
|
+
|
|
263
|
+
usage:
|
|
264
|
+
cli_demo: "python3 python/{wagon}/{feature}/composition.py"
|
|
265
|
+
http_server: "python3 python/{wagon}/{feature}/composition.py --mode http"
|
|
266
|
+
swagger_ui: "Open http://127.0.0.1:8000/docs for interactive testing"
|
|
267
|
+
|
|
268
|
+
contract_alignment:
|
|
269
|
+
principle: "Pydantic models MUST match contract JSON schemas field-for-field"
|
|
270
|
+
|
|
271
|
+
validation:
|
|
272
|
+
- "Response model fields match contract artifact schema"
|
|
273
|
+
- "Field types match JSON schema types (string→str, number→float, boolean→bool)"
|
|
274
|
+
- "Required fields marked with ..."
|
|
275
|
+
- "Optional fields marked with Optional[T]"
|
|
276
|
+
- "Default values match contract defaults"
|
|
277
|
+
|
|
278
|
+
example_mapping: |
|
|
279
|
+
# Contract schema: contracts/mechanic/timebank/remaining.schema.json
|
|
280
|
+
{
|
|
281
|
+
"remaining_seconds": {"type": "number", "description": "..."},
|
|
282
|
+
"is_low": {"type": "boolean"},
|
|
283
|
+
"preset": {"type": "string", "enum": ["blitz", "rapid", "classical"]},
|
|
284
|
+
"artifact_name": {"type": "string", "default": "mechanic:timebank.remaining"}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
# Pydantic model (MUST match)
|
|
288
|
+
class TimebankRemainingResponse(BaseModel):
|
|
289
|
+
remaining_seconds: float = Field(..., description="...")
|
|
290
|
+
is_low: bool
|
|
291
|
+
preset: Optional[str]
|
|
292
|
+
artifact_name: str = Field(default="mechanic:timebank.remaining")
|
|
293
|
+
|
|
294
|
+
green_phase_simplifications:
|
|
295
|
+
description: "Acceptable shortcuts in GREEN phase with TODO markers for REFACTOR"
|
|
296
|
+
|
|
297
|
+
allowed:
|
|
298
|
+
state_management:
|
|
299
|
+
green: "Global variable: _instance = None"
|
|
300
|
+
refactor: "TODO(REFACTOR): Use Redis/database for state management"
|
|
301
|
+
|
|
302
|
+
authentication:
|
|
303
|
+
green: "Skip authentication in GREEN"
|
|
304
|
+
refactor: "TODO(REFACTOR): Add JWT authentication with FastAPI Depends"
|
|
305
|
+
|
|
306
|
+
error_handling:
|
|
307
|
+
green: "Basic try/except with 500 responses"
|
|
308
|
+
refactor: "TODO(REFACTOR): Add custom exception handlers and status codes"
|
|
309
|
+
|
|
310
|
+
validation:
|
|
311
|
+
green: "Pydantic validates input automatically"
|
|
312
|
+
refactor: "TODO(REFACTOR): Add domain-level business rule validation"
|
|
313
|
+
|
|
314
|
+
cors:
|
|
315
|
+
green: "Allow all origins: CORSMiddleware(allow_origins=['*'])"
|
|
316
|
+
refactor: "TODO(REFACTOR): Configure proper CORS for production"
|
|
317
|
+
|
|
318
|
+
swagger_ui:
|
|
319
|
+
description: "Auto-generated interactive API documentation"
|
|
320
|
+
|
|
321
|
+
benefits:
|
|
322
|
+
- "Instant manual testing without writing test code"
|
|
323
|
+
- "Interactive request builder with example values"
|
|
324
|
+
- "Response schema visualization"
|
|
325
|
+
- "Contract validation via example requests"
|
|
326
|
+
- "Stakeholder demos without separate frontend"
|
|
327
|
+
|
|
328
|
+
customization: |
|
|
329
|
+
app = FastAPI(
|
|
330
|
+
title="{Wagon} API",
|
|
331
|
+
description="...",
|
|
332
|
+
version="0.1.0",
|
|
333
|
+
docs_url="/docs", # Swagger UI
|
|
334
|
+
redoc_url="/redoc", # ReDoc alternative
|
|
335
|
+
openapi_url="/openapi.json" # OpenAPI schema
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
api_tagging:
|
|
339
|
+
rule: "All endpoints MUST use wagon name in Title Case as single tag"
|
|
340
|
+
pattern: 'tags=["{Wagon Name}"]'
|
|
341
|
+
|
|
342
|
+
examples:
|
|
343
|
+
- 'burn_timebank → "Burn Timebank"'
|
|
344
|
+
- 'play_match → "Play Match"'
|
|
345
|
+
- 'juggle_domains → "Juggle Domains"'
|
|
346
|
+
|
|
347
|
+
anti_patterns:
|
|
348
|
+
- '"match-lifecycle" (kebab-case, not wagon name)'
|
|
349
|
+
- '"Impact Domains" (incorrect wagon name)'
|
|
350
|
+
- 'Multiple tags per wagon ("Burn Time", "Handle Timeout")'
|
|
351
|
+
|
|
352
|
+
rationale: "Wagon-based grouping aligns with domain boundaries and provides clear ownership in Swagger UI"
|
|
353
|
+
|
|
354
|
+
reference_implementation:
|
|
355
|
+
file: "python/burn_timebank/track_remaining/src/presentation/controllers/remaining_controller.py"
|
|
356
|
+
composition: "python/burn_timebank/track_remaining/composition.py"
|
|
357
|
+
note: "Study this as the canonical example"
|
|
358
|
+
|
|
359
|
+
# --------------------------------------------------------------------------
|
|
360
|
+
# TYPESCRIPT SUPABASE EDGE FUNCTIONS
|
|
361
|
+
# --------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
typescript_supabase:
|
|
364
|
+
version_support: "Deno 1.30+, Supabase Edge Functions"
|
|
365
|
+
|
|
366
|
+
file_structure:
|
|
367
|
+
handler: "supabase/functions/{wagon}/{feature}/index.ts"
|
|
368
|
+
types: "supabase/functions/{wagon}/{feature}/types.ts (if needed)"
|
|
369
|
+
urn_marker: "// urn: component:{wagon}:{feature}.handler.backend.presentation"
|
|
370
|
+
|
|
371
|
+
pattern:
|
|
372
|
+
description: "Thin edge function handler calling domain logic"
|
|
373
|
+
|
|
374
|
+
minimal_example: |
|
|
375
|
+
// urn: component:authenticate-identity:validate-credentials.handler.backend.presentation
|
|
376
|
+
// Runtime: supabase
|
|
377
|
+
// Purpose: HTTP endpoint for credential validation
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* ValidateCredentialsHandler - Supabase Edge Function
|
|
381
|
+
*
|
|
382
|
+
* Contract: identity:credentials.validation
|
|
383
|
+
*/
|
|
384
|
+
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
|
|
385
|
+
import { corsHeaders } from "../_shared/cors.ts"
|
|
386
|
+
|
|
387
|
+
// Type matching contract schema
|
|
388
|
+
interface ValidationRequest {
|
|
389
|
+
username: string
|
|
390
|
+
password: string
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
interface ValidationResponse {
|
|
394
|
+
valid: boolean
|
|
395
|
+
user_id?: string
|
|
396
|
+
artifact_name: string
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
serve(async (req: Request) => {
|
|
400
|
+
// CORS preflight
|
|
401
|
+
if (req.method === "OPTIONS") {
|
|
402
|
+
return new Response("ok", { headers: corsHeaders })
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
// Parse request
|
|
407
|
+
const body: ValidationRequest = await req.json()
|
|
408
|
+
|
|
409
|
+
// GREEN: Simple validation logic
|
|
410
|
+
// TODO(REFACTOR): Call domain service via use case
|
|
411
|
+
const valid = body.username.length > 0 && body.password.length >= 8
|
|
412
|
+
|
|
413
|
+
// Build response matching contract
|
|
414
|
+
const response: ValidationResponse = {
|
|
415
|
+
valid,
|
|
416
|
+
user_id: valid ? "user-123" : undefined,
|
|
417
|
+
artifact_name: "identity:credentials.validation"
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return new Response(
|
|
421
|
+
JSON.stringify(response),
|
|
422
|
+
{
|
|
423
|
+
headers: {
|
|
424
|
+
...corsHeaders,
|
|
425
|
+
"Content-Type": "application/json"
|
|
426
|
+
},
|
|
427
|
+
status: 200
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
} catch (error) {
|
|
432
|
+
return new Response(
|
|
433
|
+
JSON.stringify({ error: error.message }),
|
|
434
|
+
{
|
|
435
|
+
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
436
|
+
status: 500
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
green_simplifications:
|
|
443
|
+
allowed:
|
|
444
|
+
- "Inline validation logic (move to domain in REFACTOR)"
|
|
445
|
+
- "Simple error handling (enhance in REFACTOR)"
|
|
446
|
+
- "Allow all CORS origins (restrict in REFACTOR)"
|
|
447
|
+
- "No authentication (add Supabase Auth in REFACTOR)"
|
|
448
|
+
|
|
449
|
+
reference:
|
|
450
|
+
note: "Supabase functions are THIN by design - keep logic in Python backend"
|
|
451
|
+
|
|
452
|
+
# ============================================================================
|
|
453
|
+
# CLI PATTERNS
|
|
454
|
+
# ============================================================================
|
|
455
|
+
|
|
456
|
+
cli_pattern:
|
|
457
|
+
description: "Command-line interface patterns for feature demos and testing"
|
|
458
|
+
|
|
459
|
+
python_argparse:
|
|
460
|
+
pattern: |
|
|
461
|
+
# composition.py with CLI argument parsing
|
|
462
|
+
import argparse
|
|
463
|
+
|
|
464
|
+
def main():
|
|
465
|
+
parser = argparse.ArgumentParser(
|
|
466
|
+
description="{Feature} - {description}"
|
|
467
|
+
)
|
|
468
|
+
parser.add_argument("param", type=float, help="...")
|
|
469
|
+
parser.add_argument("--preset", choices=["a", "b"], default="a")
|
|
470
|
+
parser.add_argument("--verbose", action="store_true")
|
|
471
|
+
|
|
472
|
+
args = parser.parse_args()
|
|
473
|
+
|
|
474
|
+
# Wire dependencies and run
|
|
475
|
+
entity = DomainEntity(args.param)
|
|
476
|
+
result = entity.execute()
|
|
477
|
+
|
|
478
|
+
# Format output
|
|
479
|
+
print(f"Result: {result}")
|
|
480
|
+
|
|
481
|
+
rich_output:
|
|
482
|
+
description: "Enhanced terminal output per refactor.convention.yaml"
|
|
483
|
+
|
|
484
|
+
patterns:
|
|
485
|
+
colors:
|
|
486
|
+
green: "✅ Success indicators"
|
|
487
|
+
yellow: "⚠️ Warning states"
|
|
488
|
+
red: "❌ Error states"
|
|
489
|
+
cyan: "ℹ️ Info messages"
|
|
490
|
+
|
|
491
|
+
progress_bars:
|
|
492
|
+
pattern: "[████████░░░░] 80.0% (80/100)"
|
|
493
|
+
usage: "Time depletion, resource consumption"
|
|
494
|
+
|
|
495
|
+
animations:
|
|
496
|
+
dots: "Processing... (animated dots)"
|
|
497
|
+
delays: "Small delays (0.3s) for realism"
|
|
498
|
+
|
|
499
|
+
structure:
|
|
500
|
+
sections: "Clear headers with ======="
|
|
501
|
+
indentation: "Hierarchical output structure"
|
|
502
|
+
summaries: "Final summary tables"
|
|
503
|
+
|
|
504
|
+
reference_implementation:
|
|
505
|
+
file: "python/burn_timebank/wagon.py"
|
|
506
|
+
note: "Rich CLI feedback with colors, progress bars, delays"
|
|
507
|
+
|
|
508
|
+
# ============================================================================
|
|
509
|
+
# VALIDATION
|
|
510
|
+
# ============================================================================
|
|
511
|
+
|
|
512
|
+
validation:
|
|
513
|
+
description: "Automated checks for presentation layer compliance"
|
|
514
|
+
|
|
515
|
+
validator_location: "atdd/coder/validate_presentation.py"
|
|
516
|
+
|
|
517
|
+
checks:
|
|
518
|
+
fastapi_controller:
|
|
519
|
+
- "File has URN marker: # urn: component:{wagon}:{feature}.{Name}Controller.backend.presentation"
|
|
520
|
+
- "Imports FastAPI and Pydantic"
|
|
521
|
+
- "Defines Pydantic response model with Field(...) descriptors"
|
|
522
|
+
- "Response model has artifact_name field"
|
|
523
|
+
- "Has @app.get or @app.post decorators"
|
|
524
|
+
- "Endpoints have summary and tags"
|
|
525
|
+
|
|
526
|
+
contract_alignment:
|
|
527
|
+
- "Pydantic model fields match contract schema fields"
|
|
528
|
+
- "Field types match (number→float, boolean→bool)"
|
|
529
|
+
- "Required fields marked with ..."
|
|
530
|
+
- "Optional fields marked with Optional[T]"
|
|
531
|
+
|
|
532
|
+
composition_integration:
|
|
533
|
+
- "composition.py has mode parameter ('cli' or 'http')"
|
|
534
|
+
- "HTTP mode imports controller and calls uvicorn.run"
|
|
535
|
+
- "CLI mode runs domain demo without HTTP"
|
|
536
|
+
|
|
537
|
+
green_simplifications:
|
|
538
|
+
- "Global state variables have TODO(REFACTOR) comments"
|
|
539
|
+
- "Missing auth has TODO(REFACTOR) marker"
|
|
540
|
+
- "Simple error handling has TODO(REFACTOR) marker"
|
|
541
|
+
|
|
542
|
+
unified_game_server:
|
|
543
|
+
description: "python/game.py must aggregate all wagon presentation layers"
|
|
544
|
+
file: "python/game.py"
|
|
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()"
|
|
548
|
+
- "All wagon endpoints accessible via unified game server"
|
|
549
|
+
- "Validator checks: all wagons with presentation are registered in game.py"
|
|
550
|
+
|
|
551
|
+
rationale: |
|
|
552
|
+
game.py is the unified entry point for all backend services.
|
|
553
|
+
Without registration, wagon endpoints are inaccessible to game server/mobile app.
|
|
554
|
+
|
|
555
|
+
pattern: |
|
|
556
|
+
# python/game.py
|
|
557
|
+
from burn_timebank.track_remaining.src.presentation.controllers.remaining_controller import app as timebank_app
|
|
558
|
+
app.include_router(timebank_app.router, prefix="/timebank")
|
|
559
|
+
|
|
560
|
+
anti_pattern: |
|
|
561
|
+
# ❌ WRONG: Wagon has FastAPI controller but not registered in game.py
|
|
562
|
+
# Result: Endpoints exist but unreachable from unified server
|
|
563
|
+
|
|
564
|
+
usage: |
|
|
565
|
+
python3 atdd/coder/test_presentation_convention.py
|
|
566
|
+
python3 atdd/coder/test_presentation_convention.py --check-game-server
|
|
567
|
+
|
|
568
|
+
# ============================================================================
|
|
569
|
+
# CROSS-REFERENCES
|
|
570
|
+
# ============================================================================
|
|
571
|
+
|
|
572
|
+
cross_references:
|
|
573
|
+
component_types: "backend.convention.yaml::presentation (controller/route/serializer types)"
|
|
574
|
+
phase_guidance: "green.convention.yaml::composition_root (when to create)"
|
|
575
|
+
contract_dto: "dto.convention.yaml (Pydantic/Freezed models)"
|
|
576
|
+
architecture: "design.convention.yaml (Clean Architecture dependency flow)"
|
|
577
|
+
rich_cli: "refactor.convention.yaml::rich_cli_feedback (colors, progress bars)"
|
|
578
|
+
boundaries: "boundaries.convention.yaml (qualified imports)"
|
|
579
|
+
|
|
580
|
+
examples:
|
|
581
|
+
python_fastapi:
|
|
582
|
+
full: "python/burn_timebank/track_remaining/src/presentation/controllers/remaining_controller.py"
|
|
583
|
+
composition: "python/burn_timebank/track_remaining/composition.py"
|
|
584
|
+
|
|
585
|
+
cli_rich:
|
|
586
|
+
wagon: "python/burn_timebank/wagon.py"
|
|
587
|
+
note: "Rich output with colors, progress bars, delays"
|