atdd 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. atdd/__init__.py +6 -0
  2. atdd/__main__.py +4 -0
  3. atdd/cli.py +404 -0
  4. atdd/coach/__init__.py +0 -0
  5. atdd/coach/commands/__init__.py +0 -0
  6. atdd/coach/commands/add_persistence_metadata.py +215 -0
  7. atdd/coach/commands/analyze_migrations.py +188 -0
  8. atdd/coach/commands/consumers.py +720 -0
  9. atdd/coach/commands/infer_governance_status.py +149 -0
  10. atdd/coach/commands/initializer.py +177 -0
  11. atdd/coach/commands/interface.py +1078 -0
  12. atdd/coach/commands/inventory.py +565 -0
  13. atdd/coach/commands/migration.py +240 -0
  14. atdd/coach/commands/registry.py +1560 -0
  15. atdd/coach/commands/session.py +430 -0
  16. atdd/coach/commands/sync.py +405 -0
  17. atdd/coach/commands/test_interface.py +399 -0
  18. atdd/coach/commands/test_runner.py +141 -0
  19. atdd/coach/commands/tests/__init__.py +1 -0
  20. atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
  21. atdd/coach/commands/traceability.py +4264 -0
  22. atdd/coach/conventions/session.convention.yaml +754 -0
  23. atdd/coach/overlays/__init__.py +2 -0
  24. atdd/coach/overlays/claude.md +2 -0
  25. atdd/coach/schemas/config.schema.json +34 -0
  26. atdd/coach/schemas/manifest.schema.json +101 -0
  27. atdd/coach/templates/ATDD.md +282 -0
  28. atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
  29. atdd/coach/utils/__init__.py +0 -0
  30. atdd/coach/utils/graph/__init__.py +0 -0
  31. atdd/coach/utils/graph/urn.py +875 -0
  32. atdd/coach/validators/__init__.py +0 -0
  33. atdd/coach/validators/shared_fixtures.py +365 -0
  34. atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
  35. atdd/coach/validators/test_registry.py +575 -0
  36. atdd/coach/validators/test_session_validation.py +1183 -0
  37. atdd/coach/validators/test_traceability.py +448 -0
  38. atdd/coach/validators/test_update_feature_paths.py +108 -0
  39. atdd/coach/validators/test_validate_contract_consumers.py +297 -0
  40. atdd/coder/__init__.py +1 -0
  41. atdd/coder/conventions/adapter.recipe.yaml +88 -0
  42. atdd/coder/conventions/backend.convention.yaml +460 -0
  43. atdd/coder/conventions/boundaries.convention.yaml +666 -0
  44. atdd/coder/conventions/commons.convention.yaml +460 -0
  45. atdd/coder/conventions/complexity.recipe.yaml +109 -0
  46. atdd/coder/conventions/component-naming.convention.yaml +178 -0
  47. atdd/coder/conventions/design.convention.yaml +327 -0
  48. atdd/coder/conventions/design.recipe.yaml +273 -0
  49. atdd/coder/conventions/dto.convention.yaml +660 -0
  50. atdd/coder/conventions/frontend.convention.yaml +542 -0
  51. atdd/coder/conventions/green.convention.yaml +1012 -0
  52. atdd/coder/conventions/presentation.convention.yaml +587 -0
  53. atdd/coder/conventions/refactor.convention.yaml +535 -0
  54. atdd/coder/conventions/technology.convention.yaml +206 -0
  55. atdd/coder/conventions/tests/__init__.py +0 -0
  56. atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
  57. atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
  58. atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
  59. atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
  60. atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
  61. atdd/coder/conventions/thinness.recipe.yaml +82 -0
  62. atdd/coder/conventions/train.convention.yaml +325 -0
  63. atdd/coder/conventions/verification.protocol.yaml +53 -0
  64. atdd/coder/schemas/design_system.schema.json +361 -0
  65. atdd/coder/validators/__init__.py +0 -0
  66. atdd/coder/validators/test_commons_structure.py +485 -0
  67. atdd/coder/validators/test_complexity.py +416 -0
  68. atdd/coder/validators/test_cross_language_consistency.py +431 -0
  69. atdd/coder/validators/test_design_system_compliance.py +413 -0
  70. atdd/coder/validators/test_dto_testing_patterns.py +268 -0
  71. atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
  72. atdd/coder/validators/test_green_layer_dependencies.py +148 -0
  73. atdd/coder/validators/test_green_python_layer_structure.py +103 -0
  74. atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
  75. atdd/coder/validators/test_import_boundaries.py +396 -0
  76. atdd/coder/validators/test_init_file_urns.py +593 -0
  77. atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
  78. atdd/coder/validators/test_presentation_convention.py +260 -0
  79. atdd/coder/validators/test_python_architecture.py +674 -0
  80. atdd/coder/validators/test_quality_metrics.py +420 -0
  81. atdd/coder/validators/test_station_master_pattern.py +244 -0
  82. atdd/coder/validators/test_train_infrastructure.py +454 -0
  83. atdd/coder/validators/test_train_urns.py +293 -0
  84. atdd/coder/validators/test_typescript_architecture.py +616 -0
  85. atdd/coder/validators/test_usecase_structure.py +421 -0
  86. atdd/coder/validators/test_wagon_boundaries.py +586 -0
  87. atdd/conftest.py +126 -0
  88. atdd/planner/__init__.py +1 -0
  89. atdd/planner/conventions/acceptance.convention.yaml +538 -0
  90. atdd/planner/conventions/appendix.convention.yaml +187 -0
  91. atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
  92. atdd/planner/conventions/component.convention.yaml +670 -0
  93. atdd/planner/conventions/criteria.convention.yaml +141 -0
  94. atdd/planner/conventions/feature.convention.yaml +371 -0
  95. atdd/planner/conventions/interface.convention.yaml +382 -0
  96. atdd/planner/conventions/steps.convention.yaml +141 -0
  97. atdd/planner/conventions/train.convention.yaml +552 -0
  98. atdd/planner/conventions/wagon.convention.yaml +275 -0
  99. atdd/planner/conventions/wmbt.convention.yaml +258 -0
  100. atdd/planner/schemas/acceptance.schema.json +336 -0
  101. atdd/planner/schemas/appendix.schema.json +78 -0
  102. atdd/planner/schemas/component.schema.json +114 -0
  103. atdd/planner/schemas/feature.schema.json +197 -0
  104. atdd/planner/schemas/train.schema.json +192 -0
  105. atdd/planner/schemas/wagon.schema.json +281 -0
  106. atdd/planner/schemas/wmbt.schema.json +59 -0
  107. atdd/planner/validators/__init__.py +0 -0
  108. atdd/planner/validators/conftest.py +5 -0
  109. atdd/planner/validators/test_draft_wagon_registry.py +374 -0
  110. atdd/planner/validators/test_plan_cross_refs.py +240 -0
  111. atdd/planner/validators/test_plan_uniqueness.py +224 -0
  112. atdd/planner/validators/test_plan_urn_resolution.py +268 -0
  113. atdd/planner/validators/test_plan_wagons.py +174 -0
  114. atdd/planner/validators/test_train_validation.py +514 -0
  115. atdd/planner/validators/test_wagon_urn_chain.py +648 -0
  116. atdd/planner/validators/test_wmbt_consistency.py +327 -0
  117. atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
  118. atdd/tester/__init__.py +1 -0
  119. atdd/tester/conventions/artifact.convention.yaml +257 -0
  120. atdd/tester/conventions/contract.convention.yaml +1009 -0
  121. atdd/tester/conventions/filename.convention.yaml +555 -0
  122. atdd/tester/conventions/migration.convention.yaml +509 -0
  123. atdd/tester/conventions/red.convention.yaml +797 -0
  124. atdd/tester/conventions/routing.convention.yaml +51 -0
  125. atdd/tester/conventions/telemetry.convention.yaml +458 -0
  126. atdd/tester/schemas/a11y.tmpl.json +17 -0
  127. atdd/tester/schemas/artifact.schema.json +189 -0
  128. atdd/tester/schemas/contract.schema.json +591 -0
  129. atdd/tester/schemas/contract.tmpl.json +95 -0
  130. atdd/tester/schemas/db.tmpl.json +20 -0
  131. atdd/tester/schemas/e2e.tmpl.json +17 -0
  132. atdd/tester/schemas/edge_function.tmpl.json +17 -0
  133. atdd/tester/schemas/event.tmpl.json +17 -0
  134. atdd/tester/schemas/http.tmpl.json +19 -0
  135. atdd/tester/schemas/job.tmpl.json +18 -0
  136. atdd/tester/schemas/load.tmpl.json +21 -0
  137. atdd/tester/schemas/metric.tmpl.json +19 -0
  138. atdd/tester/schemas/pack.schema.json +139 -0
  139. atdd/tester/schemas/realtime.tmpl.json +20 -0
  140. atdd/tester/schemas/rls.tmpl.json +18 -0
  141. atdd/tester/schemas/script.tmpl.json +16 -0
  142. atdd/tester/schemas/sec.tmpl.json +18 -0
  143. atdd/tester/schemas/storage.tmpl.json +18 -0
  144. atdd/tester/schemas/telemetry.schema.json +128 -0
  145. atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
  146. atdd/tester/schemas/test_filename.schema.json +194 -0
  147. atdd/tester/schemas/test_intent.schema.json +179 -0
  148. atdd/tester/schemas/unit.tmpl.json +18 -0
  149. atdd/tester/schemas/visual.tmpl.json +18 -0
  150. atdd/tester/schemas/ws.tmpl.json +17 -0
  151. atdd/tester/utils/__init__.py +0 -0
  152. atdd/tester/utils/filename.py +300 -0
  153. atdd/tester/validators/__init__.py +0 -0
  154. atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
  155. atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
  156. atdd/tester/validators/conftest.py +5 -0
  157. atdd/tester/validators/coverage_gap_report.py +321 -0
  158. atdd/tester/validators/fix_dual_ac_references.py +179 -0
  159. atdd/tester/validators/remove_duplicate_lines.py +93 -0
  160. atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
  161. atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
  162. atdd/tester/validators/test_artifact_naming_category.py +307 -0
  163. atdd/tester/validators/test_contract_schema_compliance.py +706 -0
  164. atdd/tester/validators/test_contracts_structure.py +200 -0
  165. atdd/tester/validators/test_coverage_adequacy.py +797 -0
  166. atdd/tester/validators/test_dual_ac_reference.py +225 -0
  167. atdd/tester/validators/test_fixture_validity.py +372 -0
  168. atdd/tester/validators/test_isolation.py +487 -0
  169. atdd/tester/validators/test_migration_coverage.py +204 -0
  170. atdd/tester/validators/test_migration_criteria.py +276 -0
  171. atdd/tester/validators/test_migration_generation.py +116 -0
  172. atdd/tester/validators/test_python_test_naming.py +410 -0
  173. atdd/tester/validators/test_red_layer_validation.py +95 -0
  174. atdd/tester/validators/test_red_python_layer_structure.py +87 -0
  175. atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
  176. atdd/tester/validators/test_telemetry_structure.py +634 -0
  177. atdd/tester/validators/test_typescript_test_naming.py +301 -0
  178. atdd/tester/validators/test_typescript_test_structure.py +84 -0
  179. atdd-0.2.1.dist-info/METADATA +221 -0
  180. atdd-0.2.1.dist-info/RECORD +184 -0
  181. atdd-0.2.1.dist-info/WHEEL +5 -0
  182. atdd-0.2.1.dist-info/entry_points.txt +2 -0
  183. atdd-0.2.1.dist-info/licenses/LICENSE +674 -0
  184. atdd-0.2.1.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