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,1012 @@
|
|
|
1
|
+
schema_version: "1.0.1"
|
|
2
|
+
convention_id: "coder.green"
|
|
3
|
+
name: "GREEN Phase Convention"
|
|
4
|
+
description: "Behavioral rules and guardrails for making acceptance tests pass (RED → GREEN)."
|
|
5
|
+
|
|
6
|
+
green_phase:
|
|
7
|
+
goal: "Make acceptance tests pass with the thinnest vertical slice"
|
|
8
|
+
# URN naming pattern
|
|
9
|
+
urn_naming:
|
|
10
|
+
pattern: "component:{wagon}:{feature}[.{objectCamelCase}][.{side}][.{layer}][@vN]"
|
|
11
|
+
description: "Stable component URN: hierarchy via colons (kind:wagon:feature), optional facets via dots."
|
|
12
|
+
utility: "utils.graph.URNBuilder.component(wagon_id, feature_id, component_name?, side?, layer?, version?)"
|
|
13
|
+
|
|
14
|
+
parts:
|
|
15
|
+
wagon: "Parent wagon identifier (kebab-case)"
|
|
16
|
+
feature: "Parent feature identifier (kebab-case)"
|
|
17
|
+
objectCamelCase: "Component name in PascalCase or camelCase"
|
|
18
|
+
side: "Component deployment side (frontend|backend)"
|
|
19
|
+
layer: "Architectural layer (presentation|application|domain|integration)"
|
|
20
|
+
|
|
21
|
+
examples:
|
|
22
|
+
- urn: "component:resolve-dilemmas:choose-option.OptionValidator.backend.domain"
|
|
23
|
+
wagon: "resolve-dilemmas"
|
|
24
|
+
feature: "choose-option"
|
|
25
|
+
component: "OptionValidator"
|
|
26
|
+
side: "backend"
|
|
27
|
+
layer: "domain"
|
|
28
|
+
|
|
29
|
+
- urn: "component:manage-users:authenticate-user.LoginForm.frontend.presentation"
|
|
30
|
+
wagon: "manage-users"
|
|
31
|
+
feature: "authenticate-user"
|
|
32
|
+
component: "LoginForm"
|
|
33
|
+
side: "frontend"
|
|
34
|
+
layer: "presentation"
|
|
35
|
+
|
|
36
|
+
note: "Side and layer values are defined by component_type_catalog structure below"
|
|
37
|
+
|
|
38
|
+
# File Header Requirements (Traceability)
|
|
39
|
+
file_header_requirements:
|
|
40
|
+
description: "All implementation files MUST include traceability markers in file header"
|
|
41
|
+
|
|
42
|
+
component_urn_marker:
|
|
43
|
+
required: true
|
|
44
|
+
format: "# urn: component:{wagon}:{feature}.{ComponentName}.{side}.{layer}"
|
|
45
|
+
position: "First non-empty line in file (before imports)"
|
|
46
|
+
|
|
47
|
+
rationale: |
|
|
48
|
+
Component URN markers enable:
|
|
49
|
+
1. Bidirectional traceability: component ↔ wagon ↔ feature
|
|
50
|
+
2. Automated validation: verify component belongs to correct wagon/feature
|
|
51
|
+
3. Dependency analysis: track component relationships
|
|
52
|
+
4. Code navigation: jump from component to its specifications
|
|
53
|
+
5. Impact analysis: identify all components affected by feature changes
|
|
54
|
+
|
|
55
|
+
examples:
|
|
56
|
+
python: |
|
|
57
|
+
# urn: component:burn-timebank:track-remaining.TimebankMonitor.backend.domain
|
|
58
|
+
# Runtime: python
|
|
59
|
+
# Purpose: Monitor and emit timebank remaining artifacts
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
TimebankMonitor domain model.
|
|
63
|
+
"""
|
|
64
|
+
from typing import Optional
|
|
65
|
+
|
|
66
|
+
typescript: |
|
|
67
|
+
// urn: component:authenticate-identity:validate-credentials.CredentialValidator.backend.application
|
|
68
|
+
// Runtime: supabase
|
|
69
|
+
// Purpose: Validate user credentials against stored hash
|
|
70
|
+
|
|
71
|
+
import { createClient } from '@supabase/supabase-js'
|
|
72
|
+
|
|
73
|
+
dart: |
|
|
74
|
+
// urn: component:maintain-ux:provide-foundations.FoundationLoader.frontend.presentation
|
|
75
|
+
// Runtime: flutter
|
|
76
|
+
// Purpose: Load and cache UX foundation assets
|
|
77
|
+
|
|
78
|
+
import 'package:flutter/material.dart';
|
|
79
|
+
|
|
80
|
+
enforcement:
|
|
81
|
+
level: "CRITICAL"
|
|
82
|
+
validation:
|
|
83
|
+
- "Every implementation file MUST have component URN marker"
|
|
84
|
+
- "URN MUST match pattern: component:{wagon}:{feature}.{ComponentName}.{side}.{layer}"
|
|
85
|
+
- "Wagon and feature MUST exist in wagon manifest"
|
|
86
|
+
- "Component name MUST match filename (with casing transformation)"
|
|
87
|
+
- "Side MUST be 'frontend' or 'backend'"
|
|
88
|
+
- "Layer MUST be one of: domain, application, integration, presentation"
|
|
89
|
+
|
|
90
|
+
auto_validation:
|
|
91
|
+
tool: ".claude/utils/coach/manifest/validate_component_urns.py"
|
|
92
|
+
run_on: ["pre-commit", "CI"]
|
|
93
|
+
fail_on_missing: true
|
|
94
|
+
|
|
95
|
+
urn_components:
|
|
96
|
+
wagon:
|
|
97
|
+
description: "Parent wagon identifier in kebab-case"
|
|
98
|
+
pattern: "^[a-z][a-z0-9-]*$"
|
|
99
|
+
example: "burn-timebank"
|
|
100
|
+
source: "Extracted from wagon manifest plan/{wagon}/_{wagon}.yaml"
|
|
101
|
+
|
|
102
|
+
feature:
|
|
103
|
+
description: "Parent feature identifier in kebab-case"
|
|
104
|
+
pattern: "^[a-z][a-z0-9-]*$"
|
|
105
|
+
example: "track-remaining"
|
|
106
|
+
source: "Extracted from feature URN in wagon manifest"
|
|
107
|
+
|
|
108
|
+
ComponentName:
|
|
109
|
+
description: "Component name in PascalCase or camelCase"
|
|
110
|
+
pattern: "^[A-Z][a-zA-Z0-9]*$"
|
|
111
|
+
example: "TimebankMonitor"
|
|
112
|
+
derivation: "Based on artifact resource + capability suffix (see component-naming.convention.yaml)"
|
|
113
|
+
|
|
114
|
+
side:
|
|
115
|
+
description: "Deployment side"
|
|
116
|
+
values: ["frontend", "backend"]
|
|
117
|
+
example: "backend"
|
|
118
|
+
|
|
119
|
+
layer:
|
|
120
|
+
description: "Clean Architecture layer"
|
|
121
|
+
values: ["domain", "application", "integration", "presentation"]
|
|
122
|
+
example: "domain"
|
|
123
|
+
|
|
124
|
+
additional_headers:
|
|
125
|
+
runtime:
|
|
126
|
+
required: true
|
|
127
|
+
format: "# Runtime: {python|supabase|flutter}"
|
|
128
|
+
purpose: "Specify execution runtime for component"
|
|
129
|
+
source: "Read from test file header (tester already defined this)"
|
|
130
|
+
examples:
|
|
131
|
+
- "# Runtime: python"
|
|
132
|
+
- "# Runtime: supabase"
|
|
133
|
+
- "# Runtime: flutter"
|
|
134
|
+
|
|
135
|
+
purpose:
|
|
136
|
+
required: true
|
|
137
|
+
format: "# Purpose: {brief-description}"
|
|
138
|
+
purpose: "One-line description of component responsibility"
|
|
139
|
+
max_length: 80
|
|
140
|
+
examples:
|
|
141
|
+
- "# Purpose: Monitor and emit timebank remaining artifacts"
|
|
142
|
+
- "# Purpose: Validate user credentials against stored hash"
|
|
143
|
+
- "# Purpose: Load and cache UX foundation assets"
|
|
144
|
+
|
|
145
|
+
header_order:
|
|
146
|
+
1: "Component URN marker (# urn: component:...)"
|
|
147
|
+
2: "Runtime declaration (# Runtime: ...)"
|
|
148
|
+
3: "Purpose description (# Purpose: ...)"
|
|
149
|
+
4: "Blank line"
|
|
150
|
+
5: "Module docstring (optional)"
|
|
151
|
+
6: "Imports"
|
|
152
|
+
|
|
153
|
+
full_example:
|
|
154
|
+
python: |
|
|
155
|
+
# urn: component:burn-timebank:track-remaining.TimebankMonitor.backend.domain
|
|
156
|
+
# Runtime: python
|
|
157
|
+
# Purpose: Monitor and emit timebank remaining artifacts
|
|
158
|
+
|
|
159
|
+
"""
|
|
160
|
+
TimebankMonitor domain model - Track remaining time and emit artifacts.
|
|
161
|
+
|
|
162
|
+
Handles:
|
|
163
|
+
- Emit remaining artifact per contract schema
|
|
164
|
+
- Low-time warning detection
|
|
165
|
+
"""
|
|
166
|
+
from typing import Optional
|
|
167
|
+
from dataclasses import dataclass
|
|
168
|
+
|
|
169
|
+
class TimebankMonitor:
|
|
170
|
+
"""Monitors timebank and emits remaining artifacts."""
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
typescript: |
|
|
174
|
+
// urn: component:authenticate-identity:validate-credentials.CredentialValidator.backend.application
|
|
175
|
+
// Runtime: supabase
|
|
176
|
+
// Purpose: Validate user credentials against stored hash
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* CredentialValidator application service.
|
|
180
|
+
*
|
|
181
|
+
* Coordinates credential validation workflow.
|
|
182
|
+
*/
|
|
183
|
+
import { createClient } from '@supabase/supabase-js'
|
|
184
|
+
|
|
185
|
+
export class CredentialValidator {
|
|
186
|
+
// ...
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Runtime Placement (Test-Driven)
|
|
190
|
+
runtime_placement:
|
|
191
|
+
principle: "Implementation path follows test path"
|
|
192
|
+
reference: "convention:tester:red (runtime_placement section)"
|
|
193
|
+
|
|
194
|
+
workflow:
|
|
195
|
+
1_locate_test: "Find RED test file (tester already created it)"
|
|
196
|
+
2_extract_runtime: "Read test header comment for runtime decision"
|
|
197
|
+
3_extract_path: "Parse test path to derive implementation path"
|
|
198
|
+
4_validate_colocation: "Ensure test and src follow co-location pattern"
|
|
199
|
+
5_implement: "Write code at co-located src/ path"
|
|
200
|
+
|
|
201
|
+
path_derivation:
|
|
202
|
+
python:
|
|
203
|
+
test_pattern: "python/{wagon}/{feature}/tests/{layer}/test_{component}.py"
|
|
204
|
+
src_pattern: "python/{wagon}/{feature}/src/{layer}/{component}.py"
|
|
205
|
+
note: "Python uses explicit 'src' directory"
|
|
206
|
+
example:
|
|
207
|
+
test: "python/pace_dilemmas/curate_pool/tests/application/test_pool_curator.py"
|
|
208
|
+
src: "python/pace_dilemmas/curate_pool/src/application/pool_curator.py"
|
|
209
|
+
|
|
210
|
+
supabase:
|
|
211
|
+
test_pattern: "supabase/functions/{wagon}/{feature}/tests/{layer}/{component}.test.ts"
|
|
212
|
+
src_pattern: "supabase/functions/{wagon}/{feature}/{layer}/{component}.ts"
|
|
213
|
+
note: "Supabase has no 'src' directory - layers are direct children of feature"
|
|
214
|
+
example:
|
|
215
|
+
test: "supabase/functions/authenticate_identity/validate_credentials/tests/application/validate_credentials.test.ts"
|
|
216
|
+
src: "supabase/functions/authenticate_identity/validate_credentials/application/validate_credentials.ts"
|
|
217
|
+
|
|
218
|
+
enforcement:
|
|
219
|
+
mandatory:
|
|
220
|
+
- "RED test MUST exist before writing implementation"
|
|
221
|
+
- "Implementation path MUST be co-located with test"
|
|
222
|
+
- "Runtime MUST match test's documented runtime"
|
|
223
|
+
- "Layer MUST match test's layer directory"
|
|
224
|
+
|
|
225
|
+
validation:
|
|
226
|
+
- "Check test file exists at expected location"
|
|
227
|
+
- "Parse runtime from test header comment"
|
|
228
|
+
- "Verify src/ path mirrors tests/ path structure"
|
|
229
|
+
- "Ensure no cross-runtime implementations (python test → typescript src)"
|
|
230
|
+
|
|
231
|
+
header_format:
|
|
232
|
+
required_in_test: |
|
|
233
|
+
# Runtime: {python|supabase}
|
|
234
|
+
# Rationale: {reason from tester}
|
|
235
|
+
|
|
236
|
+
coder_reads:
|
|
237
|
+
runtime: "Determines which language/framework to use"
|
|
238
|
+
rationale: "Context for understanding placement decision"
|
|
239
|
+
|
|
240
|
+
anti_patterns:
|
|
241
|
+
- id: AP-WRONGRUNTIME
|
|
242
|
+
text: "Implementing in wrong runtime"
|
|
243
|
+
avoid: "Python test → TypeScript implementation (or vice versa)"
|
|
244
|
+
check: "Test runtime header MUST match implementation language"
|
|
245
|
+
|
|
246
|
+
- id: AP-NONCOLOCATION
|
|
247
|
+
text: "Breaking co-location pattern"
|
|
248
|
+
avoid: "Test in python/, implementation in supabase/"
|
|
249
|
+
check: "Src path MUST mirror test path structure"
|
|
250
|
+
|
|
251
|
+
- id: AP-NOTEST
|
|
252
|
+
text: "Implementing without test"
|
|
253
|
+
avoid: "Writing code before tester creates RED test"
|
|
254
|
+
check: "RED test MUST exist in same feature directory"
|
|
255
|
+
|
|
256
|
+
# Composition Root Pattern (Dependency Wiring)
|
|
257
|
+
composition_root:
|
|
258
|
+
description: "Hierarchical composition roots for dependency wiring at feature, wagon, and application levels"
|
|
259
|
+
|
|
260
|
+
cross_reference:
|
|
261
|
+
file: "boundaries.convention.yaml"
|
|
262
|
+
sections:
|
|
263
|
+
- "interaction.composition_roots: Full patterns and wagon isolation rules"
|
|
264
|
+
- "namespacing: Package structure and import patterns for tests/implementation"
|
|
265
|
+
note: "See boundaries.convention.yaml for complete wagon isolation architecture"
|
|
266
|
+
|
|
267
|
+
purpose: |
|
|
268
|
+
Composition roots serve as dependency injection containers and entry points.
|
|
269
|
+
They are the DIRTY GLUE that wires together CLEAN components from all layers.
|
|
270
|
+
|
|
271
|
+
COMPOSITIONAL HIERARCHY:
|
|
272
|
+
Feature-level: {feature}/composition.py (single feature)
|
|
273
|
+
Wagon-level: {wagon}/wagon.py (orchestrate features)
|
|
274
|
+
Application-level: main.py/server.py (orchestrate wagons)
|
|
275
|
+
|
|
276
|
+
location:
|
|
277
|
+
python:
|
|
278
|
+
feature_pattern: "python/{wagon}/{feature}/composition.py"
|
|
279
|
+
wagon_pattern: "python/{wagon}/wagon.py"
|
|
280
|
+
position: "Feature or wagon root directory, OUTSIDE src/"
|
|
281
|
+
rationale: "Lives outside clean architecture to clearly separate wiring from business logic"
|
|
282
|
+
|
|
283
|
+
feature_structure: |
|
|
284
|
+
python/{wagon}/{feature}/
|
|
285
|
+
├── composition.py # ⚠️ DIRTY GLUE - Feature-level wiring
|
|
286
|
+
├── tests/ # Test directory
|
|
287
|
+
└── src/ # ✅ CLEAN - 4-layer architecture
|
|
288
|
+
├── presentation/
|
|
289
|
+
├── application/
|
|
290
|
+
├── domain/
|
|
291
|
+
└── integration/
|
|
292
|
+
|
|
293
|
+
wagon_structure: |
|
|
294
|
+
python/{wagon}/
|
|
295
|
+
├── wagon.py # ⚠️ DIRTY GLUE - Wagon-level orchestration
|
|
296
|
+
├── {feature_1}/
|
|
297
|
+
│ ├── composition.py # Feature wiring
|
|
298
|
+
│ └── src/ # Clean architecture
|
|
299
|
+
├── {feature_2}/
|
|
300
|
+
│ ├── composition.py
|
|
301
|
+
│ └── src/
|
|
302
|
+
└── {feature_n}/
|
|
303
|
+
├── composition.py
|
|
304
|
+
└── src/
|
|
305
|
+
|
|
306
|
+
dart:
|
|
307
|
+
pattern: "lib/{wagon}/{feature}/composition.dart"
|
|
308
|
+
position: "Feature root directory, OUTSIDE feature src/"
|
|
309
|
+
rationale: "Lives outside clean architecture to clearly separate wiring from business logic"
|
|
310
|
+
|
|
311
|
+
structure: |
|
|
312
|
+
lib/{wagon}/{feature}/
|
|
313
|
+
├── composition.dart # ⚠️ DIRTY GLUE - Dependency wiring
|
|
314
|
+
├── tests/ # Test directory (mirror lib/)
|
|
315
|
+
└── src/ # ✅ CLEAN - 4-layer architecture
|
|
316
|
+
├── presentation/
|
|
317
|
+
├── application/
|
|
318
|
+
├── domain/
|
|
319
|
+
└── integration/
|
|
320
|
+
|
|
321
|
+
permissions:
|
|
322
|
+
allowed:
|
|
323
|
+
- "Import from ALL layers (domain, application, integration, presentation)"
|
|
324
|
+
- "Instantiate concrete implementations"
|
|
325
|
+
- "Wire dependencies together"
|
|
326
|
+
- "Violate dependency rule (e.g., import integration in presentation wiring)"
|
|
327
|
+
- "Create entry point for running feature or wagon"
|
|
328
|
+
- "Configure environment and logging"
|
|
329
|
+
- "Load configuration from environment variables"
|
|
330
|
+
- "Orchestrate multiple features (wagon.py only)"
|
|
331
|
+
|
|
332
|
+
forbidden:
|
|
333
|
+
- "Business logic (belongs in domain/application)"
|
|
334
|
+
- "Data persistence (belongs in integration)"
|
|
335
|
+
- "HTTP routing (belongs in presentation)"
|
|
336
|
+
- "Any logic that should be testable"
|
|
337
|
+
|
|
338
|
+
responsibilities:
|
|
339
|
+
feature_composition:
|
|
340
|
+
1: "Instantiate all dependencies (repositories, use cases, controllers)"
|
|
341
|
+
2: "Wire dependencies using constructor injection"
|
|
342
|
+
3: "Provide CLI entry point for running single feature"
|
|
343
|
+
4: "Support multiple modes if needed (static, dynamic, etc.)"
|
|
344
|
+
5: "Bootstrap the application and hand off to presentation layer"
|
|
345
|
+
|
|
346
|
+
wagon_composition:
|
|
347
|
+
1: "Orchestrate multiple features within wagon"
|
|
348
|
+
2: "Provide unified CLI for wagon-level testing"
|
|
349
|
+
3: "Wire feature-to-feature dependencies if needed"
|
|
350
|
+
4: "Demonstrate end-to-end wagon integration"
|
|
351
|
+
5: "Enable manual mechanic testing for game wagons"
|
|
352
|
+
|
|
353
|
+
usage:
|
|
354
|
+
feature_cli_execution:
|
|
355
|
+
description: "Run composition.py directly as feature entry point"
|
|
356
|
+
examples:
|
|
357
|
+
- command: "python3 python/resolve_dilemmas/capture_decision/composition.py"
|
|
358
|
+
mode: "Default mode (static)"
|
|
359
|
+
- command: "python3 python/resolve_dilemmas/capture_decision/composition.py dynamic"
|
|
360
|
+
mode: "Dynamic mode with argument"
|
|
361
|
+
|
|
362
|
+
wagon_cli_execution:
|
|
363
|
+
description: "Run wagon.py to orchestrate all wagon features"
|
|
364
|
+
examples:
|
|
365
|
+
- command: "python3 python/burn_timebank/wagon.py"
|
|
366
|
+
mode: "Default orchestration (all features)"
|
|
367
|
+
- command: "python3 python/burn_timebank/wagon.py --preset rapid"
|
|
368
|
+
mode: "Orchestration with arguments"
|
|
369
|
+
- command: "python3 python/burn_timebank/wagon.py --verbose"
|
|
370
|
+
mode: "Verbose mode with debug output"
|
|
371
|
+
|
|
372
|
+
import_pattern:
|
|
373
|
+
avoid: "DO NOT import composition.py or wagon.py from other modules"
|
|
374
|
+
rationale: "Composition roots should only be executed, never imported"
|
|
375
|
+
|
|
376
|
+
header_template:
|
|
377
|
+
feature_composition:
|
|
378
|
+
python:
|
|
379
|
+
format: |
|
|
380
|
+
#!/usr/bin/env python3
|
|
381
|
+
# urn: component:{wagon}:{feature}.composition.backend.infrastructure
|
|
382
|
+
# Runtime: python
|
|
383
|
+
# Purpose: Dependency injection and application bootstrap
|
|
384
|
+
|
|
385
|
+
"""
|
|
386
|
+
⚠️ COMPOSITION ROOT - DIRTY GLUE CODE ⚠️
|
|
387
|
+
|
|
388
|
+
Composition Root for {FeatureName}.
|
|
389
|
+
|
|
390
|
+
This module wires all dependencies together and provides the entry point
|
|
391
|
+
for running the feature. It's the ONLY place allowed to violate clean
|
|
392
|
+
architecture for dependency injection.
|
|
393
|
+
|
|
394
|
+
Usage:
|
|
395
|
+
python3 python/{wagon}/{feature}/composition.py [mode]
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
wagon_composition:
|
|
399
|
+
python:
|
|
400
|
+
format: |
|
|
401
|
+
#!/usr/bin/env python3
|
|
402
|
+
# urn: component:{wagon}.wagon.infrastructure
|
|
403
|
+
# Runtime: python
|
|
404
|
+
# Purpose: Wagon-level orchestration and feature integration
|
|
405
|
+
|
|
406
|
+
"""
|
|
407
|
+
⚠️ WAGON COMPOSITION ROOT - DIRTY GLUE CODE ⚠️
|
|
408
|
+
|
|
409
|
+
Orchestrates all {WagonName} features for integrated testing.
|
|
410
|
+
Lives OUTSIDE src/ and violates clean architecture by design.
|
|
411
|
+
|
|
412
|
+
This is infrastructure tooling, not production code.
|
|
413
|
+
Real application-level composition happens in the consuming application (game server).
|
|
414
|
+
|
|
415
|
+
COMPOSITIONAL HIERARCHY:
|
|
416
|
+
Feature-level: {feature}/composition.py (single feature)
|
|
417
|
+
Wagon-level: {wagon}/wagon.py (orchestrate features) ← YOU ARE HERE
|
|
418
|
+
Application-level: game_server/main.py (orchestrate wagons)
|
|
419
|
+
|
|
420
|
+
FEATURES ORCHESTRATED:
|
|
421
|
+
1. {feature_1} - {description}
|
|
422
|
+
2. {feature_2} - {description}
|
|
423
|
+
...
|
|
424
|
+
|
|
425
|
+
Usage:
|
|
426
|
+
python3 python/{wagon}/wagon.py [options]
|
|
427
|
+
"""
|
|
428
|
+
|
|
429
|
+
example: |
|
|
430
|
+
#!/usr/bin/env python3
|
|
431
|
+
# urn: component:resolve-dilemmas:capture-decision.composition.backend.infrastructure
|
|
432
|
+
# Runtime: python
|
|
433
|
+
# Purpose: Dependency injection and application bootstrap
|
|
434
|
+
|
|
435
|
+
"""
|
|
436
|
+
⚠️ COMPOSITION ROOT - DIRTY GLUE CODE ⚠️
|
|
437
|
+
|
|
438
|
+
Composition Root for Capture Decision.
|
|
439
|
+
|
|
440
|
+
Wires all dependencies and provides entry point for the quiz application.
|
|
441
|
+
This is the ONLY place allowed to violate clean architecture for DI.
|
|
442
|
+
|
|
443
|
+
Usage:
|
|
444
|
+
python3 python/resolve_dilemmas/capture_decision/composition.py # Static mode
|
|
445
|
+
python3 python/resolve_dilemmas/capture_decision/composition.py dynamic # Dynamic mode
|
|
446
|
+
"""
|
|
447
|
+
import sys
|
|
448
|
+
from pathlib import Path
|
|
449
|
+
|
|
450
|
+
# Add project root to path (REQUIRED pattern per boundaries.convention.yaml)
|
|
451
|
+
# Composition roots manipulate sys.path (they are entrypoints, never imported)
|
|
452
|
+
project_root = Path(__file__).parent.parent.parent
|
|
453
|
+
sys.path.insert(0, str(project_root))
|
|
454
|
+
|
|
455
|
+
# Domain layer - QUALIFIED imports (prevents module shadowing across wagons)
|
|
456
|
+
from resolve_dilemmas.capture_decision.src.domain.entities.choice import Choice
|
|
457
|
+
|
|
458
|
+
# Application layer
|
|
459
|
+
from resolve_dilemmas.capture_decision.src.application.use_cases.capture_choice import CaptureChoiceUseCase
|
|
460
|
+
|
|
461
|
+
# Integration layer
|
|
462
|
+
from resolve_dilemmas.capture_decision.src.integration.repositories.choice_repository import FileChoiceRepository
|
|
463
|
+
|
|
464
|
+
# Presentation layer
|
|
465
|
+
from resolve_dilemmas.capture_decision.src.presentation.controllers.quiz_controller import QuizController
|
|
466
|
+
|
|
467
|
+
def main(mode: str = "static"):
|
|
468
|
+
"""Bootstrap and run the application."""
|
|
469
|
+
# Wire dependencies (dependency injection)
|
|
470
|
+
choice_repo = FileChoiceRepository(data_dir=Path("data"))
|
|
471
|
+
capture_use_case = CaptureChoiceUseCase(repository=choice_repo)
|
|
472
|
+
controller = QuizController(
|
|
473
|
+
capture_choice=capture_use_case,
|
|
474
|
+
mode=mode
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# Run application
|
|
478
|
+
controller.run()
|
|
479
|
+
|
|
480
|
+
if __name__ == "__main__":
|
|
481
|
+
mode = sys.argv[1] if len(sys.argv) > 1 else "static"
|
|
482
|
+
main(mode)
|
|
483
|
+
|
|
484
|
+
dart:
|
|
485
|
+
format: |
|
|
486
|
+
// urn: component:{wagon}:{feature}.composition.frontend.infrastructure
|
|
487
|
+
// Runtime: flutter
|
|
488
|
+
// Purpose: Dependency injection and application bootstrap
|
|
489
|
+
|
|
490
|
+
/// Composition Root for {FeatureName}.
|
|
491
|
+
///
|
|
492
|
+
/// This module wires all dependencies together and provides the entry point
|
|
493
|
+
/// for running the feature. It's the ONLY place allowed to violate clean
|
|
494
|
+
/// architecture for dependency injection.
|
|
495
|
+
library composition;
|
|
496
|
+
|
|
497
|
+
example: |
|
|
498
|
+
// urn: component:maintain-ux:provide-foundations.composition.frontend.infrastructure
|
|
499
|
+
// Runtime: flutter
|
|
500
|
+
// Purpose: Dependency injection and application bootstrap
|
|
501
|
+
|
|
502
|
+
/// ⚠️ COMPOSITION ROOT - DIRTY GLUE CODE ⚠️
|
|
503
|
+
///
|
|
504
|
+
/// Composition Root for Provide Foundations.
|
|
505
|
+
///
|
|
506
|
+
/// Wires all dependencies and provides entry point for the foundation loader.
|
|
507
|
+
/// This is the ONLY place allowed to violate clean architecture for DI.
|
|
508
|
+
library composition;
|
|
509
|
+
|
|
510
|
+
import 'package:flutter/material.dart';
|
|
511
|
+
|
|
512
|
+
// Domain layer (relative imports allowed in composition.dart within same feature)
|
|
513
|
+
// Alternatively, use: import 'package:jel/maintain_ux/provide_foundations/src/domain/entities/foundation.dart';
|
|
514
|
+
import 'src/domain/entities/foundation.dart';
|
|
515
|
+
|
|
516
|
+
// Application layer
|
|
517
|
+
import 'src/application/use_cases/load_foundations.dart';
|
|
518
|
+
|
|
519
|
+
// Integration layer
|
|
520
|
+
import 'src/integration/repositories/foundation_repository.dart';
|
|
521
|
+
|
|
522
|
+
// Presentation layer
|
|
523
|
+
import 'src/presentation/widgets/foundation_loader_widget.dart';
|
|
524
|
+
|
|
525
|
+
/// Compose and wire dependencies for Provide Foundations feature
|
|
526
|
+
class ProvideFoundationsComposition {
|
|
527
|
+
/// Create the composed feature with all dependencies wired
|
|
528
|
+
static Widget create() {
|
|
529
|
+
// Wire dependencies (dependency injection)
|
|
530
|
+
final foundationRepo = AssetFoundationRepository();
|
|
531
|
+
final loadFoundationsUseCase = LoadFoundationsUseCase(
|
|
532
|
+
repository: foundationRepo,
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
return FoundationLoaderWidget(
|
|
536
|
+
loadFoundations: loadFoundationsUseCase,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
architectural_note:
|
|
542
|
+
principle: "Dependency Injection at the Edges"
|
|
543
|
+
description: |
|
|
544
|
+
Clean Architecture requires dependencies to flow INWARD (presentation → application → domain).
|
|
545
|
+
But someone has to INSTANTIATE and WIRE these dependencies. That's composition.py's job.
|
|
546
|
+
|
|
547
|
+
By keeping wiring separate from business logic:
|
|
548
|
+
1. Business logic stays testable (no concrete dependencies)
|
|
549
|
+
2. Dependencies can be easily swapped (different implementations)
|
|
550
|
+
3. Clear separation between WHAT (clean src/) and HOW (dirty composition.py)
|
|
551
|
+
|
|
552
|
+
quote: '"New is Glue" - composition.py is where new keyword lives'
|
|
553
|
+
|
|
554
|
+
when_to_create:
|
|
555
|
+
feature_composition:
|
|
556
|
+
timing: "During GREEN phase when implementing first use case"
|
|
557
|
+
trigger: "When you need to run the feature end-to-end"
|
|
558
|
+
scenarios:
|
|
559
|
+
- "Feature has multiple layers that need wiring"
|
|
560
|
+
- "Need CLI entry point for manual testing"
|
|
561
|
+
- "Integration tests need real dependencies"
|
|
562
|
+
|
|
563
|
+
wagon_composition:
|
|
564
|
+
timing: "After multiple features are GREEN"
|
|
565
|
+
trigger: "When you need integrated wagon testing"
|
|
566
|
+
scenarios:
|
|
567
|
+
- "Wagon has 2+ features that work together"
|
|
568
|
+
- "Need to test feature-to-feature orchestration"
|
|
569
|
+
- "Manual testing of complete wagon mechanic"
|
|
570
|
+
- "Demonstrating wagon to stakeholders"
|
|
571
|
+
note: "Not all wagons need wagon.py - only those with feature orchestration"
|
|
572
|
+
|
|
573
|
+
when_not_needed:
|
|
574
|
+
feature_composition:
|
|
575
|
+
scenarios:
|
|
576
|
+
- "Pure domain entities (no dependencies)"
|
|
577
|
+
- "Single-layer features (just presentation)"
|
|
578
|
+
- "Features only used as libraries (imported by others)"
|
|
579
|
+
|
|
580
|
+
wagon_composition:
|
|
581
|
+
scenarios:
|
|
582
|
+
- "Wagon has only 1 feature"
|
|
583
|
+
- "Features are completely independent"
|
|
584
|
+
- "No wagon-level orchestration needed"
|
|
585
|
+
|
|
586
|
+
validation:
|
|
587
|
+
feature_composition:
|
|
588
|
+
checks:
|
|
589
|
+
- "composition.py exists at feature root (not in src/)"
|
|
590
|
+
- "Imports from all layers if needed"
|
|
591
|
+
- "No business logic in composition.py"
|
|
592
|
+
- "Executable with shebang (#!/usr/bin/env python3)"
|
|
593
|
+
- "Has proper URN header with .infrastructure suffix"
|
|
594
|
+
|
|
595
|
+
wagon_composition:
|
|
596
|
+
checks:
|
|
597
|
+
- "wagon.py exists at wagon root (not in feature/)"
|
|
598
|
+
- "Orchestrates multiple features"
|
|
599
|
+
- "No business logic in wagon.py"
|
|
600
|
+
- "Executable with shebang (#!/usr/bin/env python3)"
|
|
601
|
+
- "Has proper URN header: component:{wagon}.wagon.infrastructure"
|
|
602
|
+
- "Provides CLI for integrated testing"
|
|
603
|
+
|
|
604
|
+
anti_patterns:
|
|
605
|
+
- avoid: "Business logic in composition.py or wagon.py"
|
|
606
|
+
instead: "Move to domain/application layer"
|
|
607
|
+
- avoid: "Importing composition.py or wagon.py from other modules"
|
|
608
|
+
instead: "Only execute directly as CLI entry points"
|
|
609
|
+
- avoid: "Multiple composition files per feature"
|
|
610
|
+
instead: "One composition.py per feature, use modes/arguments"
|
|
611
|
+
- avoid: "wagon.py duplicating feature logic"
|
|
612
|
+
instead: "wagon.py calls feature composition functions, doesn't reimplement"
|
|
613
|
+
|
|
614
|
+
principles:
|
|
615
|
+
- id: GP-01
|
|
616
|
+
text: "Do minimum to satisfy behavior"
|
|
617
|
+
- id: GP-02
|
|
618
|
+
text: "Defer structure and optimizations"
|
|
619
|
+
- id: GP-03
|
|
620
|
+
text: "Avoid irreversible coupling"
|
|
621
|
+
- id: GP-04
|
|
622
|
+
text: "Prefer duplication over premature abstraction"
|
|
623
|
+
- id: GP-05
|
|
624
|
+
text: "Define HOW through schema-driven architecture: validate all external data against schemas, use type-safe parsing"
|
|
625
|
+
|
|
626
|
+
guardrails:
|
|
627
|
+
- id: GR-PORTS
|
|
628
|
+
severity: error
|
|
629
|
+
rule: "All side-effects MUST be behind a tiny port/interface"
|
|
630
|
+
rationale: "Enables refactoring to clean architecture without breaking tests"
|
|
631
|
+
examples_ok:
|
|
632
|
+
- "Database access → RepositoryPort interface"
|
|
633
|
+
- "HTTP calls → HttpClientPort interface"
|
|
634
|
+
- "File I/O → FileStoragePort interface"
|
|
635
|
+
- "Queue/pub-sub → MessageBusPort interface"
|
|
636
|
+
examples_bad:
|
|
637
|
+
- "Direct DB connection in handler"
|
|
638
|
+
- "Hard-coded HTTP client in business logic"
|
|
639
|
+
- "File system calls without abstraction"
|
|
640
|
+
pattern: |
|
|
641
|
+
// Good: Side-effect behind port
|
|
642
|
+
interface OrderRepository {
|
|
643
|
+
save(order: Order): Promise<void>
|
|
644
|
+
findById(id: string): Promise<Order>
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async function handleCreateOrder(req: Request, repo: OrderRepository) {
|
|
648
|
+
const order = createOrder(req.data) // pure logic
|
|
649
|
+
await repo.save(order) // I/O behind port
|
|
650
|
+
return order
|
|
651
|
+
}
|
|
652
|
+
checks:
|
|
653
|
+
- type: grep
|
|
654
|
+
path: "presentation/**"
|
|
655
|
+
must_not_match:
|
|
656
|
+
# JavaScript/TypeScript patterns
|
|
657
|
+
- "new PgClient\\("
|
|
658
|
+
- "new Pool\\("
|
|
659
|
+
- "axios\\."
|
|
660
|
+
- "fetch\\("
|
|
661
|
+
- "fs\\.readFile"
|
|
662
|
+
- "fs\\.writeFile"
|
|
663
|
+
# Dart patterns
|
|
664
|
+
- "File\\("
|
|
665
|
+
- "HttpClient\\("
|
|
666
|
+
- "dart:io"
|
|
667
|
+
- type: grep
|
|
668
|
+
path: "domain/**"
|
|
669
|
+
must_not_match:
|
|
670
|
+
# JavaScript/TypeScript patterns
|
|
671
|
+
- "new PgClient\\("
|
|
672
|
+
- "axios\\."
|
|
673
|
+
- "fetch\\("
|
|
674
|
+
- "fs\\."
|
|
675
|
+
# Dart patterns
|
|
676
|
+
- "File\\("
|
|
677
|
+
- "HttpClient\\("
|
|
678
|
+
- "dart:io"
|
|
679
|
+
- type: grep
|
|
680
|
+
path: "lib/**"
|
|
681
|
+
must_not_match:
|
|
682
|
+
# Dart package patterns
|
|
683
|
+
- "File\\("
|
|
684
|
+
- "HttpClient\\("
|
|
685
|
+
- "dart:io"
|
|
686
|
+
|
|
687
|
+
- id: GR-PURE
|
|
688
|
+
severity: error
|
|
689
|
+
rule: "Core decision logic MUST be in a pure function callable from entrypoint"
|
|
690
|
+
rationale: "Enables testing without infrastructure"
|
|
691
|
+
pattern: |
|
|
692
|
+
// Entrypoint (can have side effects)
|
|
693
|
+
async function handleRequest(req: Request): Promise<Response> {
|
|
694
|
+
const data = await repo.fetch(req.id)
|
|
695
|
+
const result = computeResult(data) // ← PURE function
|
|
696
|
+
await repo.save(result)
|
|
697
|
+
return { status: 'ok', result }
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Pure function (no I/O, no side effects)
|
|
701
|
+
function computeResult(data: Data): Result {
|
|
702
|
+
// All business logic here
|
|
703
|
+
return { ... }
|
|
704
|
+
}
|
|
705
|
+
examples_bad:
|
|
706
|
+
- "Business logic mixed with database calls"
|
|
707
|
+
- "No pure function extractable from handler"
|
|
708
|
+
checks:
|
|
709
|
+
- type: reference
|
|
710
|
+
note: "Manually verify core logic is extractable and testable without mocks"
|
|
711
|
+
|
|
712
|
+
- id: GR-NOGLOBALS
|
|
713
|
+
severity: error
|
|
714
|
+
rule: "No globals/singletons; inject dependencies (even if manually)"
|
|
715
|
+
rationale: "Enables testing and future DI refactoring"
|
|
716
|
+
allowed:
|
|
717
|
+
- "Constructor injection (manual)"
|
|
718
|
+
- "Function parameter injection"
|
|
719
|
+
forbidden:
|
|
720
|
+
- "Global singleton DB instance"
|
|
721
|
+
- "Module-level HTTP client"
|
|
722
|
+
- "Static class members holding state"
|
|
723
|
+
pattern: |
|
|
724
|
+
// Good: Dependency injection
|
|
725
|
+
class OrderService {
|
|
726
|
+
constructor(
|
|
727
|
+
private repo: OrderRepository,
|
|
728
|
+
private emailClient: EmailClient
|
|
729
|
+
) {}
|
|
730
|
+
|
|
731
|
+
async createOrder(data: OrderData) {
|
|
732
|
+
const order = Order.create(data)
|
|
733
|
+
await this.repo.save(order)
|
|
734
|
+
await this.emailClient.send(order.confirmationEmail)
|
|
735
|
+
return order
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Bad: Global singleton
|
|
740
|
+
// export const db = new PgClient() ❌
|
|
741
|
+
checks:
|
|
742
|
+
- type: grep
|
|
743
|
+
path: "**/*.ts"
|
|
744
|
+
must_not_match:
|
|
745
|
+
- "export const db\\s*="
|
|
746
|
+
- "export const client\\s*="
|
|
747
|
+
- "global\\."
|
|
748
|
+
- "static.*client"
|
|
749
|
+
- type: grep
|
|
750
|
+
path: "**/*.dart"
|
|
751
|
+
must_not_match:
|
|
752
|
+
- "static.*database"
|
|
753
|
+
- "static.*client"
|
|
754
|
+
- "final.*=.*Database\\("
|
|
755
|
+
|
|
756
|
+
- id: GR-BASICSEC
|
|
757
|
+
severity: error
|
|
758
|
+
rule: "Basic security sanity checks (no secrets in code, validate inputs minimally)"
|
|
759
|
+
required:
|
|
760
|
+
- "No hardcoded credentials"
|
|
761
|
+
- "No secrets committed"
|
|
762
|
+
- "Basic input type validation (string/number/email format)"
|
|
763
|
+
- "No SQL injection vulnerabilities"
|
|
764
|
+
deferred_to_refactor:
|
|
765
|
+
- "Comprehensive input validation"
|
|
766
|
+
- "Rate limiting"
|
|
767
|
+
- "CSRF protection"
|
|
768
|
+
- "Detailed authorization policies"
|
|
769
|
+
checks:
|
|
770
|
+
- type: grep
|
|
771
|
+
path: "**/*"
|
|
772
|
+
must_not_match:
|
|
773
|
+
- "password\\s*=\\s*['\"][^'\"]+['\"]"
|
|
774
|
+
- "api_key\\s*=\\s*['\"][^'\"]+['\"]"
|
|
775
|
+
- "secret\\s*=\\s*['\"][^'\"]+['\"]"
|
|
776
|
+
- "Bearer [A-Za-z0-9_-]{20,}"
|
|
777
|
+
- type: grep
|
|
778
|
+
path: "presentation/**"
|
|
779
|
+
must_match:
|
|
780
|
+
- "validate|schema|zod|class-validator|joi"
|
|
781
|
+
|
|
782
|
+
- id: GR-SCHEMA
|
|
783
|
+
severity: error
|
|
784
|
+
rule: "All external data MUST be validated against schemas; use schema-driven parsing, not manual validation"
|
|
785
|
+
rationale: "Schema-first architecture ensures contracts are explicit, validated, and type-safe"
|
|
786
|
+
scope: "All boundary layers (presentation, integration)"
|
|
787
|
+
applies_to:
|
|
788
|
+
- "HTTP request bodies"
|
|
789
|
+
- "HTTP responses from external APIs"
|
|
790
|
+
- "Database query results"
|
|
791
|
+
- "File contents loaded from disk"
|
|
792
|
+
- "Environment variables"
|
|
793
|
+
- "CLI arguments"
|
|
794
|
+
benefits:
|
|
795
|
+
- "Single source of truth for data contracts"
|
|
796
|
+
- "Automatic type inference from schemas"
|
|
797
|
+
- "Runtime validation with compile-time types"
|
|
798
|
+
- "Self-documenting API contracts"
|
|
799
|
+
- "Easier refactoring when contract changes"
|
|
800
|
+
- "Prevention of data injection attacks"
|
|
801
|
+
- "Clear failure modes with structured error messages"
|
|
802
|
+
examples_ok:
|
|
803
|
+
- "Zod schema parsing HTTP request body (TypeScript)"
|
|
804
|
+
- "Freezed/json_serializable for API models (Dart)"
|
|
805
|
+
- "JSON Schema validation at integration boundaries"
|
|
806
|
+
- "Contract testing with shared schema files"
|
|
807
|
+
examples_bad:
|
|
808
|
+
- "Manual 'if' checks on req.body fields"
|
|
809
|
+
- "Type casting external data without validation"
|
|
810
|
+
- "Trusting external API response shape"
|
|
811
|
+
- "Parsing JSON without schema validation"
|
|
812
|
+
pattern: |
|
|
813
|
+
// TypeScript: Zod schema-driven parsing
|
|
814
|
+
import { z } from 'zod'
|
|
815
|
+
|
|
816
|
+
const CreateOrderSchema = z.object({
|
|
817
|
+
items: z.array(z.object({ id: z.string(), qty: z.number().positive() })),
|
|
818
|
+
shippingAddress: z.string().min(1)
|
|
819
|
+
})
|
|
820
|
+
|
|
821
|
+
type CreateOrderInput = z.infer<typeof CreateOrderSchema>
|
|
822
|
+
|
|
823
|
+
async function handleCreateOrder(req: Request): Promise<Response> {
|
|
824
|
+
// Schema validates AND provides type-safe data
|
|
825
|
+
const input: CreateOrderInput = CreateOrderSchema.parse(req.body)
|
|
826
|
+
|
|
827
|
+
// Now 'input' is guaranteed to match schema
|
|
828
|
+
const result = createOrder(input, orderRepo)
|
|
829
|
+
return { status: 'ok', data: result }
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Dart: Freezed + json_serializable
|
|
833
|
+
@freezed
|
|
834
|
+
class CreateOrderInput with _$CreateOrderInput {
|
|
835
|
+
const factory CreateOrderInput({
|
|
836
|
+
required List<OrderItem> items,
|
|
837
|
+
required String shippingAddress,
|
|
838
|
+
}) = _CreateOrderInput;
|
|
839
|
+
|
|
840
|
+
factory CreateOrderInput.fromJson(Map<String, dynamic> json) =>
|
|
841
|
+
_$CreateOrderInputFromJson(json);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Schema-driven parsing
|
|
845
|
+
Future<Response> handleCreateOrder(Request req) async {
|
|
846
|
+
final input = CreateOrderInput.fromJson(req.body); // validates structure
|
|
847
|
+
final result = await createOrder(input, orderRepo);
|
|
848
|
+
return Response.ok(result);
|
|
849
|
+
}
|
|
850
|
+
checks:
|
|
851
|
+
- type: grep
|
|
852
|
+
path: "presentation/**"
|
|
853
|
+
must_match:
|
|
854
|
+
- "zod|z\\.object|z\\.infer|freezed|@freezed|json_serializable|JsonSerializable"
|
|
855
|
+
description: "Presentation layer must use schema validation library"
|
|
856
|
+
- type: grep
|
|
857
|
+
path: "integration/**"
|
|
858
|
+
must_match:
|
|
859
|
+
- "parse|validate|fromJson|toJson|schema"
|
|
860
|
+
description: "Integration layer must validate external data"
|
|
861
|
+
- type: grep
|
|
862
|
+
path: "presentation/**/*.ts"
|
|
863
|
+
must_not_match:
|
|
864
|
+
- "req\\.body\\[|req\\.query\\[|req\\.params\\["
|
|
865
|
+
description: "Avoid direct property access on unvalidated request objects"
|
|
866
|
+
|
|
867
|
+
shortcuts:
|
|
868
|
+
- id: SH-FLAT
|
|
869
|
+
permitted: true
|
|
870
|
+
note: "Handler can call gateway/repo directly via a minimal service"
|
|
871
|
+
example: |
|
|
872
|
+
// GREEN: acceptable
|
|
873
|
+
function handleOrder(req) {
|
|
874
|
+
const result = orderService.process(req.data) // direct call
|
|
875
|
+
return result
|
|
876
|
+
}
|
|
877
|
+
refactor_target: "Handler → Use Case → Port → Gateway"
|
|
878
|
+
|
|
879
|
+
- id: SH-INMEM
|
|
880
|
+
permitted: true
|
|
881
|
+
note: "Use in-memory fakes for infrastructure"
|
|
882
|
+
example: |
|
|
883
|
+
// GREEN: acceptable
|
|
884
|
+
class InMemoryOrderRepo {
|
|
885
|
+
private orders = new Map()
|
|
886
|
+
save(order) { this.orders.set(order.id, order) }
|
|
887
|
+
find(id) { return this.orders.get(id) }
|
|
888
|
+
}
|
|
889
|
+
refactor_target: "Real DB adapter with connection pooling"
|
|
890
|
+
|
|
891
|
+
- id: SH-DUP
|
|
892
|
+
permitted: true
|
|
893
|
+
note: "Duplicate until 3rd occurrence"
|
|
894
|
+
rationale: "Wait to see pattern before abstracting"
|
|
895
|
+
example: "Same validation logic in 2 handlers is OK"
|
|
896
|
+
refactor_target: "Extract to shared validator after 3rd occurrence"
|
|
897
|
+
|
|
898
|
+
- id: SH-FILES
|
|
899
|
+
permitted: true
|
|
900
|
+
note: "Flat files/dirs (no 4-layer enforcement yet)"
|
|
901
|
+
example: |
|
|
902
|
+
feature/
|
|
903
|
+
handler.ts
|
|
904
|
+
service.ts
|
|
905
|
+
repo.ts
|
|
906
|
+
refactor_target: |
|
|
907
|
+
feature/
|
|
908
|
+
presentation/handler.ts
|
|
909
|
+
application/service.ts
|
|
910
|
+
integration/repo.ts
|
|
911
|
+
|
|
912
|
+
deliverables:
|
|
913
|
+
- id: DL-PASS
|
|
914
|
+
rule: "All acceptance tests must pass"
|
|
915
|
+
verification: "Run test suite and confirm GREEN"
|
|
916
|
+
checks:
|
|
917
|
+
- type: test_suite
|
|
918
|
+
suite: "acceptance"
|
|
919
|
+
must_be: "green"
|
|
920
|
+
|
|
921
|
+
- id: DL-ENTRY
|
|
922
|
+
rule: "Entrypoint → use-case function → ports"
|
|
923
|
+
flexibility: "Can be in same file initially"
|
|
924
|
+
example: |
|
|
925
|
+
// All in one file is OK for GREEN
|
|
926
|
+
export async function handleCreateOrder(req) {
|
|
927
|
+
const result = createOrderUseCase(req.data, orderRepo)
|
|
928
|
+
return result
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function createOrderUseCase(data, repo: OrderRepo) { // pure-ish
|
|
932
|
+
const order = new Order(data)
|
|
933
|
+
repo.save(order)
|
|
934
|
+
return order
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
- id: DL-PORTS
|
|
938
|
+
rule: "Minimal ports defined (interfaces + simplest impl/fake)"
|
|
939
|
+
example: |
|
|
940
|
+
// Port definition
|
|
941
|
+
interface OrderRepo {
|
|
942
|
+
save(order: Order): Promise<void>
|
|
943
|
+
find(id: string): Promise<Order>
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Simplest implementation
|
|
947
|
+
class InMemoryOrderRepo implements OrderRepo {
|
|
948
|
+
private orders = new Map()
|
|
949
|
+
async save(order) { this.orders.set(order.id, order) }
|
|
950
|
+
async find(id) { return this.orders.get(id) }
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
- id: DL-TODOS
|
|
954
|
+
rule: "Mark seams with TODO(REFACTOR) comments"
|
|
955
|
+
example: |
|
|
956
|
+
// TODO(REFACTOR): Extract to application/use_cases/
|
|
957
|
+
// TODO(REFACTOR): Move to domain/entities/
|
|
958
|
+
// TODO(REFACTOR): Replace InMemoryOrderRepo with PostgresOrderRepo
|
|
959
|
+
|
|
960
|
+
done_criteria:
|
|
961
|
+
- id: DC-TESTS
|
|
962
|
+
requirement: "All acceptance criteria pass"
|
|
963
|
+
- id: DC-SEAMS
|
|
964
|
+
requirement: "Clear seams exist to replace fakes/direct calls later"
|
|
965
|
+
- id: DC-PORTS
|
|
966
|
+
requirement: "Side effects abstracted behind interfaces"
|
|
967
|
+
- id: DC-PURITY
|
|
968
|
+
requirement: "Core logic extractable and testable"
|
|
969
|
+
|
|
970
|
+
anti_patterns:
|
|
971
|
+
- id: AP-OPT
|
|
972
|
+
text: "Premature optimization"
|
|
973
|
+
avoid: "Optimizing before profiling"
|
|
974
|
+
reasoning: "Wait until REFACTOR phase"
|
|
975
|
+
|
|
976
|
+
- id: AP-ABST
|
|
977
|
+
text: "Premature abstraction"
|
|
978
|
+
avoid: "Creating generic framework before 3 use cases"
|
|
979
|
+
reasoning: "Extract patterns only after repetition"
|
|
980
|
+
|
|
981
|
+
- id: AP-FWCOUPLE
|
|
982
|
+
text: "Framework coupling in business logic"
|
|
983
|
+
avoid: "Business logic importing framework classes"
|
|
984
|
+
reasoning: "Even in GREEN, keep domain pure"
|
|
985
|
+
example: "Order entity importing Express types"
|
|
986
|
+
|
|
987
|
+
- id: AP-MISSINGPORTS
|
|
988
|
+
text: "Missing port abstraction"
|
|
989
|
+
avoid: "Direct database/HTTP calls without interface"
|
|
990
|
+
reasoning: "Blocks refactoring; violates mandatory guardrail GR-PORTS"
|
|
991
|
+
|
|
992
|
+
handoff_to_refactor:
|
|
993
|
+
trigger: "All tests GREEN"
|
|
994
|
+
checklist:
|
|
995
|
+
- "✓ Tests passing"
|
|
996
|
+
- "✓ Ports defined"
|
|
997
|
+
- "✓ Pure functions identified"
|
|
998
|
+
- "✓ TODOs marked for refactoring"
|
|
999
|
+
- "✓ No mandatory guardrail violations"
|
|
1000
|
+
next_phase: "refactor.convention.yaml"
|
|
1001
|
+
|
|
1002
|
+
ci_gates:
|
|
1003
|
+
description: "Automated enforcement in CI pipeline"
|
|
1004
|
+
on: "pull_request"
|
|
1005
|
+
require:
|
|
1006
|
+
- GR-PORTS
|
|
1007
|
+
- GR-NOGLOBALS
|
|
1008
|
+
- GR-BASICSEC
|
|
1009
|
+
- GR-SCHEMA
|
|
1010
|
+
- DL-PASS
|
|
1011
|
+
optional_warnings:
|
|
1012
|
+
- GR-PURE
|