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,460 @@
|
|
|
1
|
+
version: "1.0"
|
|
2
|
+
name: "Commons Convention"
|
|
3
|
+
description: |
|
|
4
|
+
Cross-stack commons module structure for shared utilities.
|
|
5
|
+
Defines consistent naming and architecture patterns for shared code across Python and Frontend.
|
|
6
|
+
|
|
7
|
+
rationale: |
|
|
8
|
+
Wagons share cross-cutting concerns (validation, events, HTTP clients, auth) that don't belong
|
|
9
|
+
to any single wagon. The 'commons' module provides a canonical location for these utilities
|
|
10
|
+
with consistent naming across all technology stacks.
|
|
11
|
+
|
|
12
|
+
ARCHITECTURAL PRINCIPLES:
|
|
13
|
+
- Use 'commons' consistently across Python and Frontend (not 'shared' or 'core')
|
|
14
|
+
- Dependencies point inward: integration -> application -> domain
|
|
15
|
+
- Domain layer must be framework-agnostic (no preact, flask, etc.)
|
|
16
|
+
|
|
17
|
+
STRUCTURAL PATTERN DIVERGENCE (Intentional):
|
|
18
|
+
Python uses FEATURE-FIRST organization:
|
|
19
|
+
- Complex features (events/) have their own layers internally
|
|
20
|
+
- Simple utilities (resilience/, validation.py) are flat
|
|
21
|
+
- Matches Python package ecosystem conventions
|
|
22
|
+
|
|
23
|
+
Frontend uses LAYER-FIRST organization:
|
|
24
|
+
- All code organized by architectural layer at root
|
|
25
|
+
- Enables structural enforcement of layer dependencies
|
|
26
|
+
- Matches React/Preact ecosystem conventions
|
|
27
|
+
|
|
28
|
+
Both patterns enforce the same dependency rules; only the directory structure differs.
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# STRUCTURAL PATTERN RATIONALE
|
|
32
|
+
# ============================================================================
|
|
33
|
+
|
|
34
|
+
structure_patterns:
|
|
35
|
+
divergence_rationale: |
|
|
36
|
+
The Python and Frontend commons use different organizational patterns.
|
|
37
|
+
This is INTENTIONAL and reflects ecosystem conventions and complexity differences.
|
|
38
|
+
|
|
39
|
+
python:
|
|
40
|
+
pattern: "feature-first"
|
|
41
|
+
description: |
|
|
42
|
+
Complex shared features use wagon-like structure with internal layers.
|
|
43
|
+
Simple utilities are flat at the commons root.
|
|
44
|
+
|
|
45
|
+
rationale:
|
|
46
|
+
- events/ is a complex feature with ports and adapters (mini-wagon pattern)
|
|
47
|
+
- resilience/ contains simple utilities (retry, circuit_breaker) - flat is appropriate
|
|
48
|
+
- validation.py is a pure utility - flat is appropriate
|
|
49
|
+
- Matches Python package ecosystem conventions
|
|
50
|
+
|
|
51
|
+
categories:
|
|
52
|
+
complex_features:
|
|
53
|
+
description: "Features with ports/adapters use internal layer structure"
|
|
54
|
+
examples:
|
|
55
|
+
- path: "events/"
|
|
56
|
+
structure: "events/src/{application/ports, integration/queues}"
|
|
57
|
+
reason: "Event bus abstraction needs port/adapter separation"
|
|
58
|
+
|
|
59
|
+
simple_utilities:
|
|
60
|
+
description: "Pure utilities are flat at commons root"
|
|
61
|
+
examples:
|
|
62
|
+
- path: "resilience/"
|
|
63
|
+
contents: ["retry.py", "circuit_breaker.py"]
|
|
64
|
+
reason: "No layering needed for pure utility functions"
|
|
65
|
+
- path: "validation.py"
|
|
66
|
+
reason: "Single-file utility for validation types"
|
|
67
|
+
|
|
68
|
+
shared_types:
|
|
69
|
+
description: "Domain types shared across features"
|
|
70
|
+
examples:
|
|
71
|
+
- path: "domain/enums/"
|
|
72
|
+
reason: "Enumerations used by multiple wagons"
|
|
73
|
+
|
|
74
|
+
frontend:
|
|
75
|
+
pattern: "layer-first"
|
|
76
|
+
description: |
|
|
77
|
+
All shared code organized by architectural layer at the root level.
|
|
78
|
+
Features are spread across layers but grouped by subdirectory within each layer.
|
|
79
|
+
|
|
80
|
+
rationale:
|
|
81
|
+
- Explicit layer directories enable structural auditing (domain has no framework imports)
|
|
82
|
+
- Matches React/Preact ecosystem patterns (hooks/, context/, etc.)
|
|
83
|
+
- Frontend cross-cutting concerns are simpler than backend
|
|
84
|
+
- TypeScript path aliases work well with layer-based imports (@commons/domain)
|
|
85
|
+
|
|
86
|
+
categories:
|
|
87
|
+
domain_layer:
|
|
88
|
+
description: "Framework-agnostic types and errors"
|
|
89
|
+
contents:
|
|
90
|
+
- "domain/auth/types.ts" # User, AuthContextValue
|
|
91
|
+
- "domain/errors.ts" # ApiError, NotFoundError, etc.
|
|
92
|
+
audit: "No preact, react, or @tanstack imports allowed"
|
|
93
|
+
|
|
94
|
+
application_layer:
|
|
95
|
+
description: "Hooks, context, orchestration"
|
|
96
|
+
contents:
|
|
97
|
+
- "application/auth/" # AuthContext, AuthProvider, useAuth
|
|
98
|
+
- "application/query/" # QueryClientConfig
|
|
99
|
+
audit: "May import from domain, preact/hooks allowed"
|
|
100
|
+
|
|
101
|
+
integration_layer:
|
|
102
|
+
description: "External clients and adapters"
|
|
103
|
+
contents:
|
|
104
|
+
- "integration/http/" # HttpClient
|
|
105
|
+
- "integration/supabase/" # Supabase client
|
|
106
|
+
audit: "May import from domain and application"
|
|
107
|
+
|
|
108
|
+
common_principles:
|
|
109
|
+
- principle: "Domain layer purity"
|
|
110
|
+
description: "Domain layer has no framework imports in both stacks"
|
|
111
|
+
python_enforcement: "No flask, fastapi, django imports"
|
|
112
|
+
frontend_enforcement: "No preact, react, @tanstack, @maintain-ux imports"
|
|
113
|
+
|
|
114
|
+
- principle: "Dependency direction"
|
|
115
|
+
description: "Dependencies point inward: integration -> application -> domain"
|
|
116
|
+
applies_to: "Both stacks"
|
|
117
|
+
|
|
118
|
+
- principle: "Consistent naming"
|
|
119
|
+
description: "Use 'commons' (not 'shared', 'core', 'lib') in all stacks"
|
|
120
|
+
applies_to: "Both stacks"
|
|
121
|
+
|
|
122
|
+
cross_references:
|
|
123
|
+
backend_layers:
|
|
124
|
+
- file: "backend.convention.yaml"
|
|
125
|
+
note: "Defines 4-layer architecture (domain, application, integration, presentation)"
|
|
126
|
+
|
|
127
|
+
frontend_layers:
|
|
128
|
+
- file: "frontend.convention.yaml"
|
|
129
|
+
note: "Defines frontend-specific layer patterns"
|
|
130
|
+
|
|
131
|
+
wagon_boundaries:
|
|
132
|
+
- file: "boundaries.convention.yaml"
|
|
133
|
+
note: "Defines qualified import patterns"
|
|
134
|
+
|
|
135
|
+
# ============================================================================
|
|
136
|
+
# NAMING CONVENTION
|
|
137
|
+
# ============================================================================
|
|
138
|
+
|
|
139
|
+
naming:
|
|
140
|
+
canonical_name: "commons"
|
|
141
|
+
description: "Use 'commons' consistently across all technology stacks"
|
|
142
|
+
|
|
143
|
+
paths:
|
|
144
|
+
python: "python/commons/"
|
|
145
|
+
frontend: "web/src/commons/"
|
|
146
|
+
supabase: "supabase/functions/commons/"
|
|
147
|
+
|
|
148
|
+
forbidden_names:
|
|
149
|
+
- "shared" # Inconsistent with Python
|
|
150
|
+
- "core" # Too generic
|
|
151
|
+
- "common" # Singular form
|
|
152
|
+
- "utils" # Implies utility functions only
|
|
153
|
+
- "lib" # Too generic
|
|
154
|
+
|
|
155
|
+
path_aliases:
|
|
156
|
+
frontend:
|
|
157
|
+
alias: "@commons"
|
|
158
|
+
resolution: "./src/commons"
|
|
159
|
+
config_files:
|
|
160
|
+
- "web/tsconfig.json"
|
|
161
|
+
- "web/vite.config.ts"
|
|
162
|
+
|
|
163
|
+
# ============================================================================
|
|
164
|
+
# LAYER STRUCTURE
|
|
165
|
+
# ============================================================================
|
|
166
|
+
|
|
167
|
+
layers:
|
|
168
|
+
domain:
|
|
169
|
+
description: "Framework-agnostic types, entities, errors, value objects"
|
|
170
|
+
allowed_imports: []
|
|
171
|
+
forbidden_imports:
|
|
172
|
+
python: ["flask", "fastapi", "django"]
|
|
173
|
+
frontend: ["preact", "react", "@tanstack", "@maintain-ux"]
|
|
174
|
+
|
|
175
|
+
python_contents:
|
|
176
|
+
- "domain/enums/" # Shared enumerations
|
|
177
|
+
- "validation.py" # ValidationResult, Result[T]
|
|
178
|
+
|
|
179
|
+
frontend_contents:
|
|
180
|
+
- "domain/auth/types.ts" # User, AuthContextValue
|
|
181
|
+
- "domain/errors.ts" # ApiError, NotFoundError, etc.
|
|
182
|
+
|
|
183
|
+
component_suffixes:
|
|
184
|
+
python: ["*.py"]
|
|
185
|
+
frontend: ["*types.ts", "*-vo.ts", "errors.ts"]
|
|
186
|
+
|
|
187
|
+
application:
|
|
188
|
+
description: "Hooks, context, orchestration logic, ports"
|
|
189
|
+
allowed_imports: ["domain"]
|
|
190
|
+
forbidden_imports:
|
|
191
|
+
frontend: ["preact"] # Only preact/hooks allowed, not preact core
|
|
192
|
+
|
|
193
|
+
python_contents:
|
|
194
|
+
- "events/src/application/ports/" # EventBusPort
|
|
195
|
+
|
|
196
|
+
frontend_contents:
|
|
197
|
+
- "application/auth/" # AuthContext, AuthProvider, useAuth
|
|
198
|
+
- "application/query/" # QueryClientConfig
|
|
199
|
+
|
|
200
|
+
component_suffixes:
|
|
201
|
+
python: ["*_port.py", "*_service.py", "protocols.py"]
|
|
202
|
+
frontend: ["*Context.tsx", "*Provider.tsx", "use*.ts", "*Config.ts"]
|
|
203
|
+
|
|
204
|
+
integration:
|
|
205
|
+
description: "External clients, adapters, infrastructure implementations"
|
|
206
|
+
allowed_imports: ["domain", "application"]
|
|
207
|
+
|
|
208
|
+
python_contents:
|
|
209
|
+
- "events/src/integration/queues/" # InMemoryEventQueue
|
|
210
|
+
- "events/src/integration/adapters/" # Event adapters
|
|
211
|
+
- "resilience/" # retry.py, circuit_breaker.py
|
|
212
|
+
|
|
213
|
+
frontend_contents:
|
|
214
|
+
- "integration/http/" # HttpClient
|
|
215
|
+
- "integration/supabase/" # Supabase client singleton
|
|
216
|
+
|
|
217
|
+
component_suffixes:
|
|
218
|
+
python: ["*_client.py", "*_adapter.py", "*_queue.py", "*_repository.py"]
|
|
219
|
+
frontend: ["*Client.ts", "*-client.ts", "client.ts"]
|
|
220
|
+
|
|
221
|
+
# ============================================================================
|
|
222
|
+
# DEPENDENCY RULES
|
|
223
|
+
# ============================================================================
|
|
224
|
+
|
|
225
|
+
dependency_rules:
|
|
226
|
+
description: "Dependencies point inward only"
|
|
227
|
+
|
|
228
|
+
allowed_edges:
|
|
229
|
+
- from: application
|
|
230
|
+
to: [domain]
|
|
231
|
+
- from: integration
|
|
232
|
+
to: [domain, application]
|
|
233
|
+
|
|
234
|
+
forbidden_edges:
|
|
235
|
+
- from: domain
|
|
236
|
+
to: [application, integration]
|
|
237
|
+
reason: "Domain must be pure - no external dependencies"
|
|
238
|
+
|
|
239
|
+
- from: application
|
|
240
|
+
to: [integration]
|
|
241
|
+
reason: "Application uses ports; integration implements them"
|
|
242
|
+
|
|
243
|
+
# ============================================================================
|
|
244
|
+
# STACK-SPECIFIC PATTERNS
|
|
245
|
+
# ============================================================================
|
|
246
|
+
|
|
247
|
+
stacks:
|
|
248
|
+
python:
|
|
249
|
+
path: "python/commons/"
|
|
250
|
+
structure: |
|
|
251
|
+
python/commons/
|
|
252
|
+
├── __init__.py # urn:jel:commons
|
|
253
|
+
├── validation.py # Domain: ValidationResult, Result[T]
|
|
254
|
+
├── domain/
|
|
255
|
+
│ └── enums/ # Domain: Shared enumerations
|
|
256
|
+
├── events/
|
|
257
|
+
│ └── src/
|
|
258
|
+
│ ├── application/ports/ # Application: EventBusPort
|
|
259
|
+
│ └── integration/
|
|
260
|
+
│ ├── queues/ # Integration: InMemoryEventQueue
|
|
261
|
+
│ └── adapters/ # Integration: Event adapters
|
|
262
|
+
└── resilience/
|
|
263
|
+
├── retry.py # Integration: Retry with backoff
|
|
264
|
+
└── circuit_breaker.py # Integration: Circuit breaker
|
|
265
|
+
|
|
266
|
+
frontend:
|
|
267
|
+
path: "web/src/commons/"
|
|
268
|
+
structure: |
|
|
269
|
+
web/src/commons/
|
|
270
|
+
├── index.ts # Public API barrel
|
|
271
|
+
├── domain/
|
|
272
|
+
│ ├── index.ts # Domain exports
|
|
273
|
+
│ ├── auth/
|
|
274
|
+
│ │ └── types.ts # User, AuthContextValue
|
|
275
|
+
│ └── errors.ts # ApiError, NotFoundError, etc.
|
|
276
|
+
├── application/
|
|
277
|
+
│ ├── index.ts # Application exports
|
|
278
|
+
│ ├── auth/
|
|
279
|
+
│ │ ├── index.ts
|
|
280
|
+
│ │ ├── AuthContext.tsx
|
|
281
|
+
│ │ ├── AuthProvider.tsx
|
|
282
|
+
│ │ └── useAuth.ts
|
|
283
|
+
│ └── query/
|
|
284
|
+
│ ├── index.ts
|
|
285
|
+
│ └── QueryClientConfig.ts
|
|
286
|
+
└── integration/
|
|
287
|
+
├── index.ts # Integration exports
|
|
288
|
+
├── http/
|
|
289
|
+
│ ├── index.ts
|
|
290
|
+
│ └── HttpClient.ts
|
|
291
|
+
└── supabase/
|
|
292
|
+
├── index.ts
|
|
293
|
+
└── client.ts
|
|
294
|
+
|
|
295
|
+
# ============================================================================
|
|
296
|
+
# IMPORT PATTERNS
|
|
297
|
+
# ============================================================================
|
|
298
|
+
|
|
299
|
+
import_patterns:
|
|
300
|
+
frontend:
|
|
301
|
+
from_wagons:
|
|
302
|
+
description: "Import from @commons/{layer}"
|
|
303
|
+
examples:
|
|
304
|
+
- "import { HttpClient } from '@commons/integration';"
|
|
305
|
+
- "import { NotFoundError } from '@commons/domain';"
|
|
306
|
+
- "import { useAuth, AuthProvider } from '@commons/application';"
|
|
307
|
+
|
|
308
|
+
forbidden:
|
|
309
|
+
- pattern: "@shared/*"
|
|
310
|
+
reason: "Legacy naming - use @commons instead"
|
|
311
|
+
- pattern: "../shared/*"
|
|
312
|
+
reason: "Legacy relative import - use @commons alias"
|
|
313
|
+
- pattern: "./shared/*"
|
|
314
|
+
reason: "Legacy relative import - use @commons alias"
|
|
315
|
+
|
|
316
|
+
python:
|
|
317
|
+
from_wagons:
|
|
318
|
+
description: "Import with qualified path from commons"
|
|
319
|
+
examples:
|
|
320
|
+
- "from commons.validation import ValidationResult"
|
|
321
|
+
- "from commons.events.src.application.ports.event_bus_port import EventBusPort"
|
|
322
|
+
- "from commons.resilience.retry import retry_with_backoff"
|
|
323
|
+
|
|
324
|
+
# ============================================================================
|
|
325
|
+
# ENFORCEMENT
|
|
326
|
+
# ============================================================================
|
|
327
|
+
|
|
328
|
+
enforcement:
|
|
329
|
+
validators:
|
|
330
|
+
location: "atdd/coder/validators/test_commons_structure.py"
|
|
331
|
+
|
|
332
|
+
specs:
|
|
333
|
+
# Cross-stack validation
|
|
334
|
+
- id: "SPEC-CODER-COMMONS-0001"
|
|
335
|
+
description: "Commons exists in both Python and Frontend"
|
|
336
|
+
validates: "Consistent naming across stacks"
|
|
337
|
+
|
|
338
|
+
- id: "SPEC-CODER-COMMONS-0003"
|
|
339
|
+
description: "Old 'shared' directory does not exist"
|
|
340
|
+
validates: "Migration from shared to commons complete"
|
|
341
|
+
|
|
342
|
+
# Frontend structure validation (layer-first)
|
|
343
|
+
- id: "SPEC-CODER-COMMONS-0002"
|
|
344
|
+
description: "Frontend commons has domain/application/integration layers"
|
|
345
|
+
validates: "Layer-first structure for frontend"
|
|
346
|
+
|
|
347
|
+
- id: "SPEC-CODER-COMMONS-0004"
|
|
348
|
+
description: "Path aliases use @commons not @shared"
|
|
349
|
+
validates: "Correct path alias configuration"
|
|
350
|
+
|
|
351
|
+
- id: "SPEC-CODER-COMMONS-0005"
|
|
352
|
+
description: "No imports from @shared or ./shared"
|
|
353
|
+
validates: "All imports migrated to @commons"
|
|
354
|
+
|
|
355
|
+
- id: "SPEC-CODER-COMMONS-0006"
|
|
356
|
+
description: "Frontend domain layer has no framework imports"
|
|
357
|
+
validates: "Domain layer purity (no preact, react, @tanstack)"
|
|
358
|
+
|
|
359
|
+
- id: "SPEC-CODER-COMMONS-0007"
|
|
360
|
+
description: "Frontend commons has proper barrel exports (index.ts)"
|
|
361
|
+
validates: "Public API structure"
|
|
362
|
+
|
|
363
|
+
# Python structure validation (feature-first)
|
|
364
|
+
- id: "SPEC-CODER-COMMONS-0008"
|
|
365
|
+
description: "Python commons has __init__.py files"
|
|
366
|
+
validates: "Python package structure"
|
|
367
|
+
|
|
368
|
+
- id: "SPEC-CODER-COMMONS-0009"
|
|
369
|
+
description: "Python events feature has internal layer structure"
|
|
370
|
+
validates: "Feature-first pattern for complex features"
|
|
371
|
+
|
|
372
|
+
- id: "SPEC-CODER-COMMONS-0010"
|
|
373
|
+
description: "Python resilience is flat (no internal layers)"
|
|
374
|
+
validates: "Flat structure for simple utilities"
|
|
375
|
+
|
|
376
|
+
- id: "SPEC-CODER-COMMONS-0011"
|
|
377
|
+
description: "Python domain layer has no framework imports"
|
|
378
|
+
validates: "Domain layer purity (no flask, fastapi, django)"
|
|
379
|
+
|
|
380
|
+
ci_checks:
|
|
381
|
+
- name: "commons_naming"
|
|
382
|
+
description: "Verify 'commons' naming used, not 'shared'"
|
|
383
|
+
|
|
384
|
+
- name: "commons_frontend_layers"
|
|
385
|
+
description: "Verify layer-first structure in frontend commons"
|
|
386
|
+
|
|
387
|
+
- name: "commons_python_features"
|
|
388
|
+
description: "Verify feature-first structure in python commons"
|
|
389
|
+
|
|
390
|
+
- name: "commons_imports"
|
|
391
|
+
description: "Verify @commons path alias used"
|
|
392
|
+
|
|
393
|
+
- name: "commons_domain_purity"
|
|
394
|
+
description: "Verify domain layers have no framework imports"
|
|
395
|
+
|
|
396
|
+
# ============================================================================
|
|
397
|
+
# MIGRATION GUIDE
|
|
398
|
+
# ============================================================================
|
|
399
|
+
|
|
400
|
+
migration:
|
|
401
|
+
from_shared_to_commons:
|
|
402
|
+
description: "Steps to migrate from 'shared' to 'commons' naming"
|
|
403
|
+
|
|
404
|
+
steps:
|
|
405
|
+
- step: 1
|
|
406
|
+
action: "Create commons directory with 3-layer structure"
|
|
407
|
+
command: |
|
|
408
|
+
mkdir -p web/src/commons/domain/auth
|
|
409
|
+
mkdir -p web/src/commons/application/auth
|
|
410
|
+
mkdir -p web/src/commons/application/query
|
|
411
|
+
mkdir -p web/src/commons/integration/http
|
|
412
|
+
mkdir -p web/src/commons/integration/supabase
|
|
413
|
+
|
|
414
|
+
- step: 2
|
|
415
|
+
action: "Move files to appropriate layers"
|
|
416
|
+
mapping:
|
|
417
|
+
- from: "shared/auth/types.ts"
|
|
418
|
+
to: "commons/domain/auth/types.ts"
|
|
419
|
+
- from: "shared/api/errors.ts"
|
|
420
|
+
to: "commons/domain/errors.ts"
|
|
421
|
+
- from: "shared/auth/AuthContext.tsx"
|
|
422
|
+
to: "commons/application/auth/AuthContext.tsx"
|
|
423
|
+
- from: "shared/auth/useAuth.ts"
|
|
424
|
+
to: "commons/application/auth/useAuth.ts"
|
|
425
|
+
- from: "shared/auth/AuthProvider.tsx"
|
|
426
|
+
to: "commons/application/auth/AuthProvider.tsx"
|
|
427
|
+
- from: "shared/query/QueryClientConfig.ts"
|
|
428
|
+
to: "commons/application/query/QueryClientConfig.ts"
|
|
429
|
+
- from: "shared/api/HttpClient.ts"
|
|
430
|
+
to: "commons/integration/http/HttpClient.ts"
|
|
431
|
+
- from: "shared/supabase/client.ts"
|
|
432
|
+
to: "commons/integration/supabase/client.ts"
|
|
433
|
+
|
|
434
|
+
- step: 3
|
|
435
|
+
action: "Update path aliases in config files"
|
|
436
|
+
files:
|
|
437
|
+
- "web/tsconfig.json"
|
|
438
|
+
- "web/vite.config.ts"
|
|
439
|
+
change: "@shared -> @commons"
|
|
440
|
+
|
|
441
|
+
- step: 4
|
|
442
|
+
action: "Update all imports"
|
|
443
|
+
patterns:
|
|
444
|
+
- from: "@shared/api"
|
|
445
|
+
to: "@commons/integration"
|
|
446
|
+
- from: "@shared/auth"
|
|
447
|
+
to: "@commons/application"
|
|
448
|
+
- from: "../shared/auth"
|
|
449
|
+
to: "@commons/application"
|
|
450
|
+
|
|
451
|
+
- step: 5
|
|
452
|
+
action: "Delete old shared directory"
|
|
453
|
+
command: "rm -rf web/src/shared"
|
|
454
|
+
|
|
455
|
+
- step: 6
|
|
456
|
+
action: "Run verification"
|
|
457
|
+
commands:
|
|
458
|
+
- "cd web && npx tsc --noEmit"
|
|
459
|
+
- "cd web && npm test"
|
|
460
|
+
- "./atdd/atdd.py --test coder -k commons"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
recipe: complexity
|
|
2
|
+
pattern: "Complexity Reduction (Split/Extract)"
|
|
3
|
+
category: domain
|
|
4
|
+
source: "Refactoring: Improving the Design of Existing Code (Martin Fowler)"
|
|
5
|
+
utils: "complexity.py"
|
|
6
|
+
|
|
7
|
+
applies_when:
|
|
8
|
+
- "ATDD audit flags excessive complexity"
|
|
9
|
+
- "Complex boolean conditionals (cyclomatic > 10)"
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- step: 1
|
|
13
|
+
what: "Create base complexity reduction interface"
|
|
14
|
+
where: "domain/complexity/base.py"
|
|
15
|
+
template: |
|
|
16
|
+
class ComplexityRule:
|
|
17
|
+
"""Base class for composable complexity reduction rules."""
|
|
18
|
+
|
|
19
|
+
def is_satisfied_by(self, candidate) -> bool:
|
|
20
|
+
"""Check if candidate satisfies this rule."""
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
def and_(self, other: 'ComplexityRule') -> 'ComplexityRule':
|
|
24
|
+
return AndRule(self, other)
|
|
25
|
+
|
|
26
|
+
def or_(self, other: 'ComplexityRule') -> 'ComplexityRule':
|
|
27
|
+
return OrRule(self, other)
|
|
28
|
+
|
|
29
|
+
def not_(self) -> 'ComplexityRule':
|
|
30
|
+
return NotRule(self)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AndRule(ComplexityRule):
|
|
34
|
+
def __init__(self, left: ComplexityRule, right: ComplexityRule):
|
|
35
|
+
self.left = left
|
|
36
|
+
self.right = right
|
|
37
|
+
|
|
38
|
+
def is_satisfied_by(self, candidate) -> bool:
|
|
39
|
+
return self.left.is_satisfied_by(candidate) and self.right.is_satisfied_by(candidate)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class OrRule(ComplexityRule):
|
|
43
|
+
def __init__(self, left: ComplexityRule, right: ComplexityRule):
|
|
44
|
+
self.left = left
|
|
45
|
+
self.right = right
|
|
46
|
+
|
|
47
|
+
def is_satisfied_by(self, candidate) -> bool:
|
|
48
|
+
return self.left.is_satisfied_by(candidate) or self.right.is_satisfied_by(candidate)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class NotRule(ComplexityRule):
|
|
52
|
+
def __init__(self, rule: ComplexityRule):
|
|
53
|
+
self.rule = rule
|
|
54
|
+
|
|
55
|
+
def is_satisfied_by(self, candidate) -> bool:
|
|
56
|
+
return not self.rule.is_satisfied_by(candidate)
|
|
57
|
+
|
|
58
|
+
- step: 2
|
|
59
|
+
what: "Extract each complex conditional to named rule class"
|
|
60
|
+
where: "domain/complexity/{rule}.py"
|
|
61
|
+
example: |
|
|
62
|
+
# Before (complex conditional):
|
|
63
|
+
if customer.is_premium and order.total > 1000 or customer.is_first_time:
|
|
64
|
+
apply_discount()
|
|
65
|
+
|
|
66
|
+
# After (extract to rules):
|
|
67
|
+
class IsPremiumCustomerRule(ComplexityRule):
|
|
68
|
+
def is_satisfied_by(self, candidate) -> bool:
|
|
69
|
+
return candidate['customer'].is_premium
|
|
70
|
+
|
|
71
|
+
class MinimumOrderRule(ComplexityRule):
|
|
72
|
+
def __init__(self, min_amount: Money):
|
|
73
|
+
self.min_amount = min_amount
|
|
74
|
+
|
|
75
|
+
def is_satisfied_by(self, candidate) -> bool:
|
|
76
|
+
return candidate['order'].total >= self.min_amount
|
|
77
|
+
|
|
78
|
+
class IsFirstTimeCustomerRule(ComplexityRule):
|
|
79
|
+
def is_satisfied_by(self, candidate) -> bool:
|
|
80
|
+
return candidate['customer'].is_first_time
|
|
81
|
+
|
|
82
|
+
- step: 3
|
|
83
|
+
what: "Compose complexity rules with and_/or_/not_"
|
|
84
|
+
template: |
|
|
85
|
+
# Compose
|
|
86
|
+
eligible_for_discount = (
|
|
87
|
+
IsPremiumCustomerRule()
|
|
88
|
+
.and_(MinimumOrderRule(Money.from_dollars(1000)))
|
|
89
|
+
.or_(IsFirstTimeCustomerRule())
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Use
|
|
93
|
+
if eligible_for_discount.is_satisfied_by({'customer': customer, 'order': order}):
|
|
94
|
+
apply_discount()
|
|
95
|
+
|
|
96
|
+
verify:
|
|
97
|
+
- run: pytest -xvs tests/domain/complexity/
|
|
98
|
+
expect: GREEN
|
|
99
|
+
|
|
100
|
+
requirements:
|
|
101
|
+
- "Complexity rules MUST be side-effect free (pure functions)"
|
|
102
|
+
- "Each rule testable in isolation"
|
|
103
|
+
- "Composition works (and_/or_/not_)"
|
|
104
|
+
|
|
105
|
+
final_verify:
|
|
106
|
+
- "Cyclomatic complexity reduced below threshold"
|
|
107
|
+
- "Boolean logic is explicit and named"
|
|
108
|
+
- "Rules reusable across validation/filtering/queries"
|
|
109
|
+
- "All tests GREEN"
|