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,206 @@
1
+ version: "1.0"
2
+ name: "Technology Stack Convention"
3
+ description: "Lean map of default technologies with rationales, constraints, and deviation guidance"
4
+
5
+ technology:
6
+ backend:
7
+ presentation:
8
+ default: "Supabase Edge Functions (TypeScript on Deno)"
9
+ rationale: "Runs next to the Supabase cluster, inherits auth, scales automatically, and keeps request-response code minimal."
10
+ use_for: "Low-latency APIs, auth-guarded endpoints, simple business logic glued to Supabase data."
11
+ avoid_when: "Workloads need long-lived processes, heavy native dependencies, or complex multi-service orchestration."
12
+ alternatives:
13
+ - option: "Dedicated Node.js/Express service on managed infrastructure"
14
+ status: "approved"
15
+ use_when: "Domain logic depends on heavy SDKs, long-lived processes, or custom runtimes Supabase cannot host."
16
+ tradeoff: "Adds infrastructure surface area and manual scaling."
17
+ application:
18
+ default: "Supabase Edge Functions orchestrating Supabase Auth and database access"
19
+ rationale: "Keeps authorization, data access, and domain logic in a single managed layer with row-level security enforced."
20
+ use_for: "Transactional CRUD flows, admin automation, server-side validation."
21
+ avoid_when: "Throughput or scheduling requirements exceed Edge quotas, or logic depends on non-Supabase data sources."
22
+ alternatives:
23
+ - option: "Supabase Scheduled Functions"
24
+ status: "approved"
25
+ use_when: "Workloads are cron-like or event-driven and finish within Supabase execution limits."
26
+ tradeoff: "Still bound by platform quotas and limited runtime flexibility."
27
+ - option: "External task runner (Temporal, BullMQ, etc.)"
28
+ status: "approved"
29
+ use_when: "Jobs require retries, long execution windows, or integration with non-Supabase systems."
30
+ tradeoff: "Demands separate deployment, observability, and on-call coverage."
31
+ domain:
32
+ default: "PostgreSQL stored procedures and row-level security policies"
33
+ rationale: "Guarantees business rules live with the data for consistency and integrity enforcement."
34
+ use_for: "Invariants, data validation, and domain events that must run on every write."
35
+ avoid_when: "Logic needs complex external integrations or is easier to iterate outside SQL."
36
+ alternatives:
37
+ - option: "Edge Function domain services"
38
+ status: "approved"
39
+ use_when: "Rules benefit from TypeScript tooling or orchestration with external APIs."
40
+ tradeoff: "Moves guarantees out of the database; must guard against bypass paths."
41
+ integration:
42
+ default:
43
+ - "Supabase Realtime"
44
+ - "Gun.js mesh"
45
+ rationale: "Supabase anchors authoritative state while Gun.js delivers sub-100ms peer sync, offline-first UX, and lower infrastructure costs."
46
+ use_for: "Multiplayer messaging, collaborative gameplay state, or any feature needing optimistic UI with later checkpointing."
47
+ avoid_when: "Workflows demand strict ACID semantics end-to-end or the team cannot support Gun.js expertise."
48
+ constraints:
49
+ team_size: "2-5 engineers"
50
+ budget_focus: "Minimize server and database spend"
51
+ user_expectations: "Mobile-first clients expect instant feedback"
52
+ infrastructure: "Client: Gun.js peer + Supabase Flutter client (embedded); Relay: 2-3 WebSocket VPS; Validation: Node.js service (containerized); Persistence: Supabase (PostgreSQL, Auth, Realtime)"
53
+ data_partition:
54
+ ephemeral_store: "Gun.js handles: chat (last 100), presence, optimistic UI writes (<5KB/message)"
55
+ authoritative_store: "Supabase handles: auth, balances, settlements, checkpoint history, analytics"
56
+ tradeoffs:
57
+ accepted_risks: ["Eventual consistency Gun ↔ Supabase", "Smaller Gun.js hiring pool", "Distributed logging complexity"]
58
+ mitigations: ["Validator enforces checkpoint frequency", "Health checks on relay uptime and lag", "Keep Gun graph structures shallow"]
59
+ operations:
60
+ monitoring_focus:
61
+ relay: "Uptime >99.5%, mesh connectivity"
62
+ checkpoint_lag: "P99 <5s"
63
+ persistence: "Error rate, query latency <200ms P95"
64
+ cost_comparison:
65
+ hybrid: "~$40/mo (2 relay VPS + Supabase checkpoints)"
66
+ managed_only: "~$950/mo at 1M messages/day (Supabase Realtime only)"
67
+ savings: "~$10,920/year; revisit if traffic >1M messages/day"
68
+ fallback_paths:
69
+ - trigger: "Checkpoint lag exceeds SLA or team lacks Gun expertise"
70
+ action: "Migrate to Supabase Realtime + Redis cache"
71
+ alternatives:
72
+ - option: "Supabase Realtime only"
73
+ use_when: "Offline mode optional and last-write-wins semantics acceptable"
74
+
75
+ frontend:
76
+ presentation:
77
+ default: "Flutter 3.x with custom design system"
78
+ rationale: "Single codebase delivers high-performance experiences across iOS, Android, and web."
79
+ use_for: "Feature-rich game UI, animation-heavy interfaces, and rapid UI iteration with hot reload."
80
+ avoid_when: "Team already maintains a React-native web stack or needs platform-specific native capabilities not exposed in Flutter."
81
+ alternatives:
82
+ - option: "React Native"
83
+ status: "approved"
84
+ use_when: "Team ships JavaScript mobile apps today and performance demands are moderate."
85
+ tradeoff: "Potentially lower graphics fidelity and more platform quirks for game-heavy features."
86
+ application:
87
+ default:
88
+ - "Riverpod"
89
+ - "BLoC/Cubit"
90
+ rationale: "Separates UI from state orchestration, supports testability, and matches community best practices."
91
+ use_for: "Feature modules that require deterministic state flow and clear dependency injection."
92
+ avoid_when: "Very small widgets where Provider or setState keeps maintenance lighter."
93
+ alternatives:
94
+ - option: "Provider or ValueNotifier"
95
+ status: "approved"
96
+ use_when: "Screens are simple, team velocity matters more than architecture purity."
97
+ tradeoff: "Scaling to complex flows later may require refactoring to BLoC."
98
+ domain:
99
+ default: "Dart domain models using Freezed"
100
+ rationale: "Immutability, pattern matching, and serialization with minimal boilerplate."
101
+ use_for: "Shared business rules, input validation, and data contracts across UI and services."
102
+ avoid_when: "Models originate from generated SDKs or performance-critical code paths where Freezed overhead is excessive."
103
+ alternatives:
104
+ - option: "Manual Dart classes"
105
+ status: "approved"
106
+ use_when: "Need bespoke constructors, performance tuning, or external SDK models."
107
+ tradeoff: "Lose Freezed conveniences (copyWith, unions, generated equality)."
108
+ integration:
109
+ default:
110
+ - "Supabase Flutter client"
111
+ - "Gun.js client"
112
+ rationale: "Mirrors backend hybrid stack, keeping authentication and realtime semantics consistent."
113
+ use_for: "Data access layers, multiplayer state sync, and offline caching strategies aligned with backend decisions."
114
+ avoid_when: "Application does not use Gun.js features or requires a different backend."
115
+ alternatives:
116
+ - option: "Supabase client only"
117
+ use_when: "Features interact solely with Supabase data and collaboration is minimal"
118
+
119
+ media:
120
+ presentation:
121
+ default:
122
+ video: "WebM (VP9) with MP4 (H.264) fallback for iOS <14"
123
+ images: "WebP with JPEG fallback for iOS <14"
124
+ audio: "Opus (WebM container) with AAC fallback for iOS <14"
125
+ rationale: "WebM/WebP/Opus reduce bandwidth by 30% vs MP4/JPEG/AAC; Flutter supports natively on Android/web."
126
+ use_for: "User content, game assets, profile media, background music, SFX, voiceover."
127
+ avoid_when: "User base is 90%+ iOS <14 or team lacks transcoding capability."
128
+ platform_support: "Android/web native; iOS 14+ native WebP/AAC, WebM/Opus via AVPlayer; iOS <14 serve fallback formats"
129
+ application:
130
+ default: "Client → Supabase Storage → Edge Function enqueues transcoding → FFmpeg worker → CDN delivery"
131
+ rationale: "Integrates with Supabase auth/RLS while async transcoding keeps upload fast."
132
+ use_for: "User uploads requiring format conversion and quality optimization."
133
+ avoid_when: "Media volume exceeds Supabase Storage quotas or transcoding SLA unmet."
134
+ workflow: "Client validates → Upload to Supabase Storage → Webhook triggers optimization → Transcode to primary + fallback formats → CDN caches (1yr TTL)"
135
+ alternatives:
136
+ - option: "Cloudinary/Imgix managed service"
137
+ use_when: "Team cannot maintain transcoding infrastructure or needs DRM/adaptive streaming"
138
+ domain:
139
+ default: "PostgreSQL triggers enforce quotas; Edge Functions validate format selection per tier"
140
+ rationale: "Quota and format rules live with data to prevent bypass."
141
+ use_for: "Storage quotas, quality tiers, content moderation policies."
142
+ integration:
143
+ default: "Supabase Storage + Cloudflare CDN + Containerized FFmpeg worker (Cloud Run, Fly.io)"
144
+ rationale: "Supabase Storage integrates auth; Cloudflare caches globally; FFmpeg handles standard transcoding."
145
+ use_for: "Secure storage, global delivery, format optimization."
146
+ avoid_when: "Compliance requires on-premise or volume exceeds Supabase pricing efficiency."
147
+ infrastructure: "Storage (Supabase with RLS), CDN (Cloudflare, 1yr TTL), Transcoding (serverless FFmpeg, queue-based autoscaling)"
148
+ cost_comparison: "Self-hosted ~$60/mo vs managed service ~$200/mo; savings ~$1,680/year"
149
+ fallback_paths:
150
+ - trigger: "Queue depth exceeds SLA"
151
+ action: "Scale workers or migrate to managed API"
152
+ - trigger: "iOS <14 usage <5%"
153
+ action: "Drop MP4/JPEG/AAC fallback generation"
154
+
155
+ ai:
156
+ presentation:
157
+ default: "LLM-generated text/dialog, decision evaluations, gesture/content classifications"
158
+ rationale: "AI outputs integrate with Flutter UI as text, scores, or metadata; no client-side model execution."
159
+ use_for: "Dynamic content creation, gameplay evaluation, input interpretation."
160
+ avoid_when: "Latency SLA <200ms or cost per interaction exceeds budget."
161
+ constraints: "Structured JSON output, P95 <2s interactive / <10s background, graceful degradation to cached/rule-based on failure"
162
+ application:
163
+ default: "Edge Function orchestrates LLM APIs with rate limiting, token budgets, caching (PostgreSQL/Redis), fallback chain: LLM → local model → rules"
164
+ rationale: "Centralized orchestration controls costs, latency, and observability."
165
+ use_for: "Prompt management, cost control, request routing, response validation."
166
+ avoid_when: "All AI logic can run locally on-device without quality loss."
167
+ workflow: "Client request → Edge Function validates → LLM API call → Cache response → Return JSON; on timeout/error serve cache or local model"
168
+ alternatives:
169
+ - option: "Client-side on-device ML models (TensorFlow Lite)"
170
+ use_when: "Latency critical and quality acceptable with small models"
171
+ domain:
172
+ default: "Game evaluation rules, scoring algorithms, pattern matching logic via PostgreSQL functions + Edge Function validation"
173
+ rationale: "Core AI logic lives with business rules to ensure consistency and auditability."
174
+ use_for: "Evaluation scoring formulas, decision tree traversal, outcome prediction."
175
+ avoid_when: "Logic requires heavy computation better suited to async workers or external APIs."
176
+ integration:
177
+ default: "LLM APIs (Claude, Mistral), local models (open source), knowledge graphs (PostgreSQL graph extensions)"
178
+ rationale: "LLMs for complex reasoning, local models for latency-critical tasks, graphs for structured domain logic."
179
+ use_for: "Text generation, strategic evaluation, semantic classification, content reasoning."
180
+ avoid_when: "All use cases satisfied by rule-based logic or simpler heuristics."
181
+ infrastructure: "LLM tier (Edge Function + API key rotation + PostgreSQL quotas), Local model tier (TF Lite in Flutter app), Knowledge graph tier (PostgreSQL pg_graph)"
182
+ cost_comparison: "Hybrid strategy: 80% local models, 20% LLM → ~$20/mo LLM spend vs ~$100/mo LLM-only"
183
+ fallback_paths:
184
+ - trigger: "LLM API error rate >5% or latency >5s"
185
+ action: "Switch to cached responses or local model"
186
+ - trigger: "Token budget 80% consumed mid-month"
187
+ action: "Throttle non-critical AI features; prioritize premium users"
188
+
189
+ telemetry:
190
+ observability:
191
+ default: "Sentry"
192
+ rationale: "Cross-platform error tracking, transaction tracing, and alerting with minimal setup."
193
+ use_for: "Production services and clients where regressions must surface within minutes."
194
+ avoid_when: "Throwaway experiments or spikes that never reach users."
195
+ alternatives:
196
+ - option: "Self-hosted OpenTelemetry stack"
197
+ use_when: "Regulatory rules or cost controls require keeping telemetry in-house"
198
+ analytics:
199
+ default: "PostHog"
200
+ rationale: "Event analytics, feature flags, and user journey insights integrated with Supabase/Flutter stack."
201
+ use_for: "Activation, retention, and feature adoption tracking across mobile and web clients."
202
+ avoid_when: "Internal prototypes where event instrumentation would slow delivery."
203
+
204
+ notes:
205
+ deviation_process: "Document any alternative choice in the architecture log with rationale and expected revisit date."
206
+ decision_scope: "This mapping applies to new services and major rewrites; legacy codebases may continue with their existing stack until refactored."
File without changes
@@ -0,0 +1,302 @@
1
+ """
2
+ Tests for adapter recipe.
3
+
4
+ Tests SPEC-CODER-UTL-0163 to 052, 055, 056, 057, 058
5
+ ATDD: These tests define expected behavior of adapter recipe BEFORE implementation.
6
+ """
7
+ import pytest
8
+ import yaml
9
+ from pathlib import Path
10
+ from typing import Dict, Any
11
+
12
+
13
+ # Recipe loader utilities (shared with other recipe tests)
14
+ def load_recipe(recipe_name: str) -> Dict[str, Any]:
15
+ """
16
+ Load recipe YAML file.
17
+
18
+ SPEC-CODER-UTL-0163: Load adapter recipe
19
+ """
20
+ recipe_path = Path(__file__).resolve().parents[1] / f"{recipe_name}.recipe.yaml"
21
+
22
+ if not recipe_path.exists():
23
+ raise FileNotFoundError(f"Recipe not found: {recipe_path}")
24
+
25
+ with open(recipe_path, 'r') as f:
26
+ return yaml.safe_load(f)
27
+
28
+
29
+ def check_recipe_applies(recipe_name: str, context: Dict[str, Any]) -> bool:
30
+ """
31
+ Check if recipe applies based on context.
32
+
33
+ SPEC-CODER-UTL-0164: Detect when adapter recipe applies
34
+ """
35
+ recipe = load_recipe(recipe_name)
36
+
37
+ if recipe_name == "adapter":
38
+ # adapter applies when port exists without implementation
39
+ return context.get("port_exists", False) and not context.get("adapter_exists", False)
40
+
41
+ return False
42
+
43
+
44
+ def execute_recipe_step(recipe_name: str, step: int, context: Dict[str, Any]) -> Dict[str, Any]:
45
+ """
46
+ Execute a recipe step.
47
+
48
+ SPEC-CODER-UTL-0165, 051, 052: Execute steps 1, 2, 3
49
+ """
50
+ recipe = load_recipe(recipe_name)
51
+ steps = recipe.get("steps", [])
52
+
53
+ if step < 1 or step > len(steps):
54
+ return {"success": False, "error": f"Invalid step: {step}"}
55
+
56
+ step_def = steps[step - 1]
57
+
58
+ result = {
59
+ "success": True,
60
+ "step": step,
61
+ "what": step_def.get("what", ""),
62
+ "where": step_def.get("where", ""),
63
+ "template": step_def.get("template", ""),
64
+ "naming": step_def.get("naming", ""),
65
+ "purpose": step_def.get("purpose", ""),
66
+ "next_action": "continue" if step < len(steps) else "verify_final"
67
+ }
68
+
69
+ # Step-specific additions
70
+ if step == 1:
71
+ result["port_found"] = True
72
+
73
+ return result
74
+
75
+
76
+ def select_recipe(smells: Dict[str, Any]) -> Dict[str, Any]:
77
+ """
78
+ Select recipe based on smell detection.
79
+
80
+ SPEC-CODER-UTL-0170: Map missing adapter to adapter recipe
81
+ """
82
+ # Priority 1: Handler smell → thin_handler
83
+ if smells.get("thinness", {}).get("passed", True) is False:
84
+ return {"recipe": "thin_handler", "priority": 1}
85
+
86
+ # Priority 2: Complexity smell → specification
87
+ if smells.get("complexity", {}).get("passed", True) is False:
88
+ return {"recipe": "specification", "priority": 2}
89
+
90
+ # Priority 3: Missing adapter → adapter
91
+ if smells.get("missing_adapter", False):
92
+ return {"recipe": "adapter", "priority": 3}
93
+
94
+ return {"recipe": None, "priority": 0}
95
+
96
+
97
+ def verify_recipe_step(step_result: Dict[str, Any], test_status: str) -> bool:
98
+ """
99
+ Verify recipe step maintains GREEN tests.
100
+
101
+ SPEC-CODER-UTL-0171: Verify recipe step maintains GREEN tests
102
+ SPEC-CODER-UTL-0173: Rollback on step failure
103
+ """
104
+ if test_status == "GREEN":
105
+ return True
106
+
107
+ # RED tests trigger rollback
108
+ if test_status == "RED":
109
+ # This would call rollback_refactor_step() in real usage
110
+ return False
111
+
112
+ return False
113
+
114
+
115
+ def verify_recipe_final(recipe_name: str, results: Dict[str, Any]) -> Dict[str, Any]:
116
+ """
117
+ Verify final state after all recipe steps.
118
+
119
+ SPEC-CODER-UTL-0172: Verify final state after all recipe steps
120
+ """
121
+ verification = {"success": True, "checks": []}
122
+
123
+ if recipe_name == "adapter":
124
+ # Final verification: port has implementation
125
+ verification["checks"].append({
126
+ "check": "port_has_implementation",
127
+ "expected": "adapter implements port interface"
128
+ })
129
+ verification["checks"].append({
130
+ "check": "mapper_exists",
131
+ "expected": "mapper isolates domain from infrastructure"
132
+ })
133
+
134
+ return verification
135
+
136
+
137
+ # TESTS
138
+
139
+ class TestAdapterRecipeLoading:
140
+ """Test SPEC-CODER-UTL-0163: Load adapter recipe"""
141
+
142
+ def test_load_recipe(self):
143
+ """Should load adapter recipe YAML"""
144
+ recipe = load_recipe("adapter")
145
+
146
+ assert recipe is not None
147
+ assert recipe["recipe"] == "adapter"
148
+ assert recipe["pattern"] == "Adapter (implements port interface)"
149
+ assert "steps" in recipe
150
+ assert len(recipe["steps"]) == 3
151
+
152
+
153
+ class TestAdapterRecipeApplies:
154
+ """Test SPEC-CODER-UTL-0164: Detect when adapter recipe applies"""
155
+
156
+ def test_recipe_applies(self):
157
+ """Should return true when port exists without adapter"""
158
+ context = {
159
+ "port_exists": True,
160
+ "adapter_exists": False
161
+ }
162
+
163
+ result = check_recipe_applies("adapter", context)
164
+
165
+ assert result is True
166
+
167
+ def test_recipe_not_applies_no_port(self):
168
+ """Should return false when port doesn't exist"""
169
+ context = {
170
+ "port_exists": False,
171
+ "adapter_exists": False
172
+ }
173
+
174
+ result = check_recipe_applies("adapter", context)
175
+
176
+ assert result is False
177
+
178
+ def test_recipe_not_applies_adapter_exists(self):
179
+ """Should return false when adapter already exists"""
180
+ context = {
181
+ "port_exists": True,
182
+ "adapter_exists": True
183
+ }
184
+
185
+ result = check_recipe_applies("adapter", context)
186
+
187
+ assert result is False
188
+
189
+
190
+ class TestAdapterRecipeSteps:
191
+ """Test SPEC-CODER-UTL-0165, 051, 052: Execute recipe steps"""
192
+
193
+ def test_execute_step_1(self):
194
+ """Should execute step 1: verify port"""
195
+ context = {"port_name": "OrderRepository"}
196
+
197
+ result = execute_recipe_step("adapter", 1, context)
198
+
199
+ assert result["success"] is True
200
+ assert "port" in result["what"].lower() or "verify" in result["what"].lower()
201
+ assert "application/" in result["where"]
202
+ assert result["port_found"] is True
203
+
204
+ def test_execute_step_2(self):
205
+ """Should execute step 2: implement adapter"""
206
+ context = {"port_found": True}
207
+
208
+ result = execute_recipe_step("adapter", 2, context)
209
+
210
+ assert result["success"] is True
211
+ assert "adapter" in result["what"].lower() or "implement" in result["what"].lower()
212
+ assert "integration/" in result["where"]
213
+ assert result["naming"] # Should have naming convention
214
+
215
+ def test_execute_step_3(self):
216
+ """Should execute step 3: create mapper"""
217
+ context = {"port_found": True, "adapter_created": True}
218
+
219
+ result = execute_recipe_step("adapter", 3, context)
220
+
221
+ assert result["success"] is True
222
+ assert "mapper" in result["what"].lower()
223
+ assert "integration/mappers/" in result["where"]
224
+ assert "isolate" in result["purpose"].lower() or "domain" in result["purpose"].lower()
225
+ assert result["next_action"] == "verify_final"
226
+
227
+
228
+ class TestAdapterRecipeSelection:
229
+ """Test SPEC-CODER-UTL-0170: Map missing adapter to adapter recipe"""
230
+
231
+ def test_select_from_smell(self):
232
+ """Should select adapter recipe when missing adapter detected"""
233
+ smells = {
234
+ "missing_adapter": True
235
+ }
236
+
237
+ result = select_recipe(smells)
238
+
239
+ assert result["recipe"] == "adapter"
240
+ assert result["priority"] == 3
241
+
242
+
243
+ class TestAdapterRecipeVerification:
244
+ """Test SPEC-CODER-UTL-0171, 057, 058: Recipe verification"""
245
+
246
+ def test_verify_step_green(self):
247
+ """Should return true when tests are GREEN"""
248
+ step_result = {"success": True, "step": 1}
249
+
250
+ result = verify_recipe_step(step_result, "GREEN")
251
+
252
+ assert result is True
253
+
254
+ def test_rollback_on_failure(self):
255
+ """Should return false and trigger rollback when tests are RED"""
256
+ step_result = {"success": True, "step": 1}
257
+
258
+ result = verify_recipe_step(step_result, "RED")
259
+
260
+ assert result is False
261
+
262
+ def test_verify_final(self):
263
+ """Should verify final state with port implementation check"""
264
+ results = {"all_steps_completed": True}
265
+
266
+ verification = verify_recipe_final("adapter", results)
267
+
268
+ assert verification["success"] is True
269
+ assert len(verification["checks"]) >= 2
270
+ assert any("port" in check["check"].lower() for check in verification["checks"])
271
+ assert any("mapper" in check["check"].lower() for check in verification["checks"])
272
+
273
+
274
+ class TestAdapterRecipeIntegration:
275
+ """Integration tests for full recipe workflow"""
276
+
277
+ def test_full_recipe_workflow(self):
278
+ """Should execute full recipe from detection to verification"""
279
+ # 1. Detect missing adapter
280
+ smells = {"missing_adapter": True}
281
+
282
+ # 2. Select recipe
283
+ selected = select_recipe(smells)
284
+ assert selected["recipe"] == "adapter"
285
+
286
+ # 3. Load recipe
287
+ recipe = load_recipe("adapter")
288
+ assert recipe is not None
289
+
290
+ # 4. Execute steps
291
+ context = {"port_name": "OrderRepository"}
292
+ for step in range(1, 4):
293
+ result = execute_recipe_step("adapter", step, context)
294
+ assert result["success"] is True
295
+
296
+ # Verify step
297
+ verified = verify_recipe_step(result, "GREEN")
298
+ assert verified is True
299
+
300
+ # 5. Final verification
301
+ verification = verify_recipe_final("adapter", {"all_steps": True})
302
+ assert verification["success"] is True