atdd 0.6.0__tar.gz → 0.7.0__tar.gz
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-0.6.0/src/atdd.egg-info → atdd-0.7.0}/PKG-INFO +1 -1
- {atdd-0.6.0 → atdd-0.7.0}/pyproject.toml +3 -1
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/schemas/config.schema.json +11 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/templates/ATDD.md +1 -1
- atdd-0.7.0/src/atdd/coach/utils/locale_phase.py +97 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/shared_fixtures.py +49 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/backend.convention.yaml +1 -1
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/boundaries.convention.yaml +9 -9
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/presentation.convention.yaml +8 -8
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/train.convention.yaml +15 -14
- atdd-0.7.0/src/atdd/coder/validators/test_i18n_runtime.py +171 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_presentation_convention.py +11 -11
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_station_master_pattern.py +16 -14
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_train_infrastructure.py +22 -14
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_wagon_boundaries.py +2 -2
- atdd-0.7.0/src/atdd/tester/schemas/locale_manifest.schema.json +53 -0
- atdd-0.7.0/src/atdd/tester/validators/test_locale_coverage.py +451 -0
- {atdd-0.6.0 → atdd-0.7.0/src/atdd.egg-info}/PKG-INFO +1 -1
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd.egg-info/SOURCES.txt +4 -0
- {atdd-0.6.0 → atdd-0.7.0}/LICENSE +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/README.md +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/setup.cfg +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/__main__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/cli.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/add_persistence_metadata.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/analyze_migrations.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/consumers.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/gate.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/infer_governance_status.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/initializer.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/interface.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/inventory.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/migration.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/registry.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/session.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/sync.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/test_interface.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/test_runner.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/tests/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/tests/test_telemetry_array_validation.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/commands/traceability.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/conventions/session.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/overlays/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/overlays/claude.md +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/schemas/manifest.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/templates/SESSION-TEMPLATE.md +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/utils/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/utils/config.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/utils/coverage_phase.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/utils/graph/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/utils/graph/urn.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/utils/repo.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/utils/train_spec_phase.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/test_enrich_wagon_registry.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/test_registry.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/test_release_versioning.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/test_session_validation.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/test_traceability.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/test_train_registry.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/test_update_feature_paths.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coach/validators/test_validate_contract_consumers.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/adapter.recipe.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/commons.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/complexity.recipe.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/component-naming.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/coverage.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/design.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/design.recipe.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/dto.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/frontend.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/green.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/refactor.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/technology.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/tests/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/tests/test_adapter_recipe.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/tests/test_complexity_recipe.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/tests/test_component_taxonomy.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/tests/test_component_urn_naming.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/tests/test_thinness_recipe.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/thinness.recipe.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/conventions/verification.protocol.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/schemas/design_system.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/conftest.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_commons_structure.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_complexity.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_cross_language_consistency.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_design_system_compliance.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_dto_testing_patterns.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_green_cross_stack_layers.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_green_layer_dependencies.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_green_python_layer_structure.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_green_supabase_layer_structure.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_hierarchy_coverage.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_import_boundaries.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_init_file_urns.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_preact_layer_boundaries.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_python_architecture.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_quality_metrics.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_train_urns.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_typescript_architecture.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/coder/validators/test_usecase_structure.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/conftest.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/acceptance.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/appendix.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/artifact-naming.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/component.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/coverage.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/criteria.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/feature.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/interface.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/steps.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/train.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/wagon.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/conventions/wmbt.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/schemas/acceptance.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/schemas/appendix.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/schemas/component.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/schemas/feature.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/schemas/train.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/schemas/wagon.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/schemas/wmbt.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/conftest.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_draft_wagon_registry.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_hierarchy_coverage.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_plan_cross_refs.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_plan_uniqueness.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_plan_urn_resolution.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_plan_wagons.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_train_validation.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_wagon_urn_chain.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_wmbt_consistency.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/planner/validators/test_wmbt_vocabulary.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/conventions/artifact.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/conventions/contract.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/conventions/coverage.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/conventions/filename.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/conventions/migration.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/conventions/red.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/conventions/routing.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/conventions/security.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/conventions/telemetry.convention.yaml +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/a11y.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/artifact.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/contract.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/contract.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/db.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/e2e.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/edge_function.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/event.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/http.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/job.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/load.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/metric.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/pack.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/realtime.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/rls.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/script.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/sec.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/storage.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/telemetry.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/telemetry_tracking_manifest.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/test_filename.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/test_intent.schema.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/unit.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/visual.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/schemas/ws.tmpl.json +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/utils/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/utils/filename.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/__init__.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/cleanup_duplicate_headers.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/cleanup_duplicate_headers_v2.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/conftest.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/coverage_gap_report.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/fix_dual_ac_references.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/remove_duplicate_lines.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_acceptance_urn_filename_mapping.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_acceptance_urn_separator.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_artifact_naming_category.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_contract_schema_compliance.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_contract_security.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_contracts_structure.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_coverage_adequacy.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_dual_ac_reference.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_fixture_validity.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_hierarchy_coverage.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_isolation.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_migration_coverage.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_migration_criteria.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_migration_generation.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_python_test_naming.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_red_layer_validation.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_red_python_layer_structure.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_red_supabase_layer_structure.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_telemetry_structure.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_train_backend_e2e.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_train_frontend_e2e.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_train_frontend_python.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_typescript_test_naming.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/tester/validators/test_typescript_test_structure.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd/version_check.py +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd.egg-info/dependency_links.txt +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd.egg-info/entry_points.txt +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd.egg-info/requires.txt +0 -0
- {atdd-0.6.0 → atdd-0.7.0}/src/atdd.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "atdd"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "ATDD Platform - Acceptance Test Driven Development toolkit"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
readme = "README.md"
|
|
@@ -39,4 +39,6 @@ markers = [
|
|
|
39
39
|
"tester: marks tests as tester validators",
|
|
40
40
|
"platform: marks tests as platform validators",
|
|
41
41
|
"security: marks tests as security validators",
|
|
42
|
+
"locale: marks tests as localization validators (Localization Manifest Spec v1)",
|
|
43
|
+
"coder: marks tests as coder phase validators",
|
|
42
44
|
]
|
|
@@ -121,6 +121,17 @@
|
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
"additionalProperties": false
|
|
124
|
+
},
|
|
125
|
+
"localization": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"description": "Localization validation settings (Localization Manifest Spec v1)",
|
|
128
|
+
"properties": {
|
|
129
|
+
"manifest": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"description": "Path to localization manifest.json relative to repo root (e.g., web/locales/manifest.json)"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"additionalProperties": false
|
|
124
135
|
}
|
|
125
136
|
},
|
|
126
137
|
"required": ["version", "release"],
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Localization Manifest Spec v1 Rollout Phase Controller.
|
|
3
|
+
|
|
4
|
+
Manages the phased rollout of localization validation rules:
|
|
5
|
+
- Phase 1 (WARNINGS_ONLY): All validators emit warnings only
|
|
6
|
+
- Phase 2 (TESTER_ENFORCEMENT): Tester phase validators (LOCALE-TEST-*) strict
|
|
7
|
+
- Phase 3 (FULL_ENFORCEMENT): All validators including Coder (LOCALE-CODE-*) strict
|
|
8
|
+
|
|
9
|
+
Usage in validators:
|
|
10
|
+
from atdd.coach.utils.locale_phase import LocalePhase, should_enforce_locale
|
|
11
|
+
|
|
12
|
+
if should_enforce_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
13
|
+
assert condition, "Error message"
|
|
14
|
+
else:
|
|
15
|
+
if not condition:
|
|
16
|
+
emit_locale_warning("LOCALE-TEST-1.1", "Warning message", LocalePhase.TESTER_ENFORCEMENT)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from enum import IntEnum
|
|
20
|
+
from typing import Optional
|
|
21
|
+
import warnings
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LocalePhase(IntEnum):
|
|
25
|
+
"""
|
|
26
|
+
Rollout phases for Localization Manifest Spec v1.
|
|
27
|
+
|
|
28
|
+
Phases are ordered by strictness level:
|
|
29
|
+
- WARNINGS_ONLY (1): All new validators emit warnings, no assertions
|
|
30
|
+
- TESTER_ENFORCEMENT (2): Tester phase validators (LOCALE-TEST-*) strict
|
|
31
|
+
- FULL_ENFORCEMENT (3): All validators including Coder (LOCALE-CODE-*) strict
|
|
32
|
+
"""
|
|
33
|
+
WARNINGS_ONLY = 1
|
|
34
|
+
TESTER_ENFORCEMENT = 2
|
|
35
|
+
FULL_ENFORCEMENT = 3
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Current rollout phase - update this to advance through phases
|
|
39
|
+
CURRENT_LOCALE_PHASE = LocalePhase.WARNINGS_ONLY
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def should_enforce_locale(validator_phase: LocalePhase) -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Check if a locale 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_locale(LocalePhase.TESTER_ENFORCEMENT):
|
|
56
|
+
assert all_keys_match, "Keys must match reference locale"
|
|
57
|
+
else:
|
|
58
|
+
if not all_keys_match:
|
|
59
|
+
emit_locale_warning("LOCALE-TEST-1.4", "Keys don't match", LocalePhase.TESTER_ENFORCEMENT)
|
|
60
|
+
"""
|
|
61
|
+
return CURRENT_LOCALE_PHASE >= validator_phase
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_current_locale_phase() -> LocalePhase:
|
|
65
|
+
"""Get the current locale rollout phase."""
|
|
66
|
+
return CURRENT_LOCALE_PHASE
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_locale_phase_name(phase: Optional[LocalePhase] = None) -> str:
|
|
70
|
+
"""Get human-readable name for a locale phase."""
|
|
71
|
+
phase = phase or CURRENT_LOCALE_PHASE
|
|
72
|
+
return {
|
|
73
|
+
LocalePhase.WARNINGS_ONLY: "Phase 1: Warnings Only",
|
|
74
|
+
LocalePhase.TESTER_ENFORCEMENT: "Phase 2: Tester Enforcement",
|
|
75
|
+
LocalePhase.FULL_ENFORCEMENT: "Phase 3: Full Enforcement",
|
|
76
|
+
}.get(phase, "Unknown Phase")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def emit_locale_warning(
|
|
80
|
+
spec_id: str,
|
|
81
|
+
message: str,
|
|
82
|
+
validator_phase: LocalePhase = LocalePhase.TESTER_ENFORCEMENT
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Emit a locale validation warning with phase context.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
spec_id: The SPEC ID (e.g., "LOCALE-TEST-1.1")
|
|
89
|
+
message: The warning message
|
|
90
|
+
validator_phase: Phase when this becomes an error
|
|
91
|
+
"""
|
|
92
|
+
phase_name = get_locale_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
|
+
)
|
|
@@ -599,3 +599,52 @@ def wagon_to_train_mapping(train_files: List[Tuple[Path, Dict]]) -> Dict[str, Li
|
|
|
599
599
|
mapping[wagon_slug].append(train_id)
|
|
600
600
|
|
|
601
601
|
return mapping
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
# ============================================================================
|
|
605
|
+
# LOCALIZATION FIXTURES (Localization Manifest Spec v1)
|
|
606
|
+
# ============================================================================
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
@pytest.fixture(scope="module")
|
|
610
|
+
def locale_manifest_path(atdd_config: Dict[str, Any]) -> Optional[Path]:
|
|
611
|
+
"""
|
|
612
|
+
Get path to localization manifest file from config.
|
|
613
|
+
|
|
614
|
+
Returns:
|
|
615
|
+
Path to manifest file, or None if localization not configured
|
|
616
|
+
"""
|
|
617
|
+
manifest_rel = atdd_config.get("localization", {}).get("manifest")
|
|
618
|
+
if not manifest_rel:
|
|
619
|
+
return None
|
|
620
|
+
return REPO_ROOT / manifest_rel
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
@pytest.fixture(scope="module")
|
|
624
|
+
def locale_manifest(locale_manifest_path: Optional[Path]) -> Optional[Dict[str, Any]]:
|
|
625
|
+
"""
|
|
626
|
+
Load localization manifest from configured path.
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
Manifest dict with reference, locales, namespaces, or None if not configured
|
|
630
|
+
"""
|
|
631
|
+
if locale_manifest_path is None:
|
|
632
|
+
return None
|
|
633
|
+
if not locale_manifest_path.exists():
|
|
634
|
+
return None
|
|
635
|
+
|
|
636
|
+
with open(locale_manifest_path) as f:
|
|
637
|
+
return json.load(f)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@pytest.fixture(scope="module")
|
|
641
|
+
def locales_dir(locale_manifest_path: Optional[Path]) -> Optional[Path]:
|
|
642
|
+
"""
|
|
643
|
+
Get locales directory (parent of manifest file).
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
Path to locales directory, or None if not configured
|
|
647
|
+
"""
|
|
648
|
+
if locale_manifest_path is None:
|
|
649
|
+
return None
|
|
650
|
+
return locale_manifest_path.parent
|
|
@@ -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
|
# ============================================================================
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
i18n runtime validation (Localization Manifest Spec v1).
|
|
3
|
+
|
|
4
|
+
Validates that runtime code uses the centralized locale manifest:
|
|
5
|
+
- LOCALE-CODE-2.1: i18nConfig.ts imports from manifest (not hardcoded arrays)
|
|
6
|
+
- LOCALE-CODE-2.2: LanguageSwitcher uses shared SUPPORTED_LOCALES
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import pytest
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from atdd.coach.utils.locale_phase import (
|
|
15
|
+
LocalePhase,
|
|
16
|
+
should_enforce_locale,
|
|
17
|
+
emit_locale_warning,
|
|
18
|
+
)
|
|
19
|
+
from atdd.coach.utils.repo import find_repo_root
|
|
20
|
+
|
|
21
|
+
# Path constants
|
|
22
|
+
REPO_ROOT = find_repo_root()
|
|
23
|
+
WEB_DIR = REPO_ROOT / "web"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _find_file(base_dir: Path, *possible_paths: str) -> Optional[Path]:
|
|
27
|
+
"""Find first existing file from list of possible paths."""
|
|
28
|
+
for rel_path in possible_paths:
|
|
29
|
+
full_path = base_dir / rel_path
|
|
30
|
+
if full_path.exists():
|
|
31
|
+
return full_path
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _read_file_content(path: Path) -> Optional[str]:
|
|
36
|
+
"""Read file content, return None on error."""
|
|
37
|
+
try:
|
|
38
|
+
return path.read_text()
|
|
39
|
+
except Exception:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.locale
|
|
44
|
+
@pytest.mark.coder
|
|
45
|
+
def test_i18n_config_uses_manifest(locale_manifest, locale_manifest_path):
|
|
46
|
+
"""
|
|
47
|
+
LOCALE-CODE-2.1: i18nConfig.ts imports from manifest (not hardcoded arrays)
|
|
48
|
+
|
|
49
|
+
Given: Web application with i18n configuration
|
|
50
|
+
When: Checking i18nConfig.ts or i18n.ts
|
|
51
|
+
Then: Configuration imports locales from manifest or shared constant
|
|
52
|
+
NOT hardcoded locale arrays like ['en', 'es', 'fr']
|
|
53
|
+
"""
|
|
54
|
+
if locale_manifest is None:
|
|
55
|
+
pytest.skip("Localization not configured")
|
|
56
|
+
|
|
57
|
+
i18n_config = _find_file(
|
|
58
|
+
WEB_DIR,
|
|
59
|
+
"src/i18nConfig.ts",
|
|
60
|
+
"src/i18n/config.ts",
|
|
61
|
+
"src/i18n.ts",
|
|
62
|
+
"src/lib/i18n.ts",
|
|
63
|
+
"src/config/i18n.ts",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if i18n_config is None:
|
|
67
|
+
pytest.skip("No i18n config file found in web/src/")
|
|
68
|
+
|
|
69
|
+
content = _read_file_content(i18n_config)
|
|
70
|
+
if content is None:
|
|
71
|
+
pytest.skip(f"Cannot read {i18n_config}")
|
|
72
|
+
|
|
73
|
+
hardcoded_array_pattern = re.compile(
|
|
74
|
+
r"(?:locales|supportedLocales|SUPPORTED_LOCALES|languages)\s*[=:]\s*\[\s*['\"][a-z]{2}",
|
|
75
|
+
re.IGNORECASE
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if hardcoded_array_pattern.search(content):
|
|
79
|
+
manifest_import_patterns = [
|
|
80
|
+
r"from\s+['\"].*manifest",
|
|
81
|
+
r"import.*manifest",
|
|
82
|
+
r"require\s*\(\s*['\"].*manifest",
|
|
83
|
+
r"SUPPORTED_LOCALES",
|
|
84
|
+
r"getSupportedLocales",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
has_manifest_usage = any(
|
|
88
|
+
re.search(pattern, content, re.IGNORECASE)
|
|
89
|
+
for pattern in manifest_import_patterns
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if not has_manifest_usage:
|
|
93
|
+
msg = (
|
|
94
|
+
f"i18n config has hardcoded locale array: {i18n_config.relative_to(REPO_ROOT)}\n"
|
|
95
|
+
f" Should import from manifest.json or use shared SUPPORTED_LOCALES constant"
|
|
96
|
+
)
|
|
97
|
+
if should_enforce_locale(LocalePhase.FULL_ENFORCEMENT):
|
|
98
|
+
pytest.fail(msg)
|
|
99
|
+
else:
|
|
100
|
+
emit_locale_warning("LOCALE-CODE-2.1", msg, LocalePhase.FULL_ENFORCEMENT)
|
|
101
|
+
pytest.skip(msg)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.locale
|
|
105
|
+
@pytest.mark.coder
|
|
106
|
+
def test_language_switcher_uses_shared_locales(locale_manifest, locale_manifest_path):
|
|
107
|
+
"""
|
|
108
|
+
LOCALE-CODE-2.2: LanguageSwitcher uses shared SUPPORTED_LOCALES
|
|
109
|
+
|
|
110
|
+
Given: Web application with language switcher component
|
|
111
|
+
When: Checking LanguageSwitcher component
|
|
112
|
+
Then: Component imports locales from shared constant or manifest
|
|
113
|
+
NOT hardcoded locale arrays
|
|
114
|
+
"""
|
|
115
|
+
if locale_manifest is None:
|
|
116
|
+
pytest.skip("Localization not configured")
|
|
117
|
+
|
|
118
|
+
switcher_patterns = [
|
|
119
|
+
"src/components/LanguageSwitcher.tsx",
|
|
120
|
+
"src/components/LocaleSwitcher.tsx",
|
|
121
|
+
"src/components/ui/LanguageSwitcher.tsx",
|
|
122
|
+
"src/components/common/LanguageSwitcher.tsx",
|
|
123
|
+
"src/features/i18n/LanguageSwitcher.tsx",
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
switcher_file = _find_file(WEB_DIR, *switcher_patterns)
|
|
127
|
+
|
|
128
|
+
if switcher_file is None:
|
|
129
|
+
switcher_files = list(WEB_DIR.rglob("*[Ll]anguage*[Ss]witcher*.tsx"))
|
|
130
|
+
if not switcher_files:
|
|
131
|
+
switcher_files = list(WEB_DIR.rglob("*[Ll]ocale*[Ss]witcher*.tsx"))
|
|
132
|
+
if switcher_files:
|
|
133
|
+
switcher_file = switcher_files[0]
|
|
134
|
+
|
|
135
|
+
if switcher_file is None:
|
|
136
|
+
pytest.skip("No LanguageSwitcher component found")
|
|
137
|
+
|
|
138
|
+
content = _read_file_content(switcher_file)
|
|
139
|
+
if content is None:
|
|
140
|
+
pytest.skip(f"Cannot read {switcher_file}")
|
|
141
|
+
|
|
142
|
+
hardcoded_array_pattern = re.compile(
|
|
143
|
+
r"(?:locales|languages|options)\s*[=:]\s*\[\s*(?:\{[^}]*locale[^}]*['\"][a-z]{2}|['\"][a-z]{2})",
|
|
144
|
+
re.IGNORECASE
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if hardcoded_array_pattern.search(content):
|
|
148
|
+
shared_patterns = [
|
|
149
|
+
r"SUPPORTED_LOCALES",
|
|
150
|
+
r"getSupportedLocales",
|
|
151
|
+
r"from\s+['\"].*manifest",
|
|
152
|
+
r"from\s+['\"].*i18n",
|
|
153
|
+
r"from\s+['\"].*config",
|
|
154
|
+
r"useLocales",
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
has_shared_usage = any(
|
|
158
|
+
re.search(pattern, content, re.IGNORECASE)
|
|
159
|
+
for pattern in shared_patterns
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if not has_shared_usage:
|
|
163
|
+
msg = (
|
|
164
|
+
f"LanguageSwitcher has hardcoded locale array: {switcher_file.relative_to(REPO_ROOT)}\n"
|
|
165
|
+
f" Should import from shared SUPPORTED_LOCALES or manifest"
|
|
166
|
+
)
|
|
167
|
+
if should_enforce_locale(LocalePhase.FULL_ENFORCEMENT):
|
|
168
|
+
pytest.fail(msg)
|
|
169
|
+
else:
|
|
170
|
+
emit_locale_warning("LOCALE-CODE-2.2", msg, LocalePhase.FULL_ENFORCEMENT)
|
|
171
|
+
pytest.skip(msg)
|
|
@@ -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"
|