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,240 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate Supabase JSONB migrations from contract schemas.
4
+
5
+ SPEC-COACH-CONV-0033: Simplified JSONB-only migration generator
6
+
7
+ Usage:
8
+ python atdd/coach/commands/migration.py # Generate all missing
9
+ python atdd/coach/commands/migration.py --contract <path> # Generate specific
10
+ python atdd/coach/commands/migration.py --validate # Check coverage only
11
+ """
12
+
13
+ import argparse
14
+ import json
15
+ from datetime import datetime
16
+ from pathlib import Path
17
+
18
+
19
+ # Path constants
20
+ REPO_ROOT = Path(__file__).resolve().parents[4]
21
+ CONTRACTS_DIR = REPO_ROOT / "contracts"
22
+ MIGRATIONS_DIR = REPO_ROOT / "supabase" / "migrations"
23
+
24
+
25
+ def contract_needs_migration(contract_path: Path) -> bool:
26
+ """
27
+ Determine if a contract needs a database migration.
28
+
29
+ Decision algorithm (ordered rules, first match wins):
30
+ 1. Explicit persistence.strategy: check if != 'none'
31
+ 2. Empty properties: len(properties) == 0 → NO
32
+ 3. Event without id: aspect ends with '*ed' AND no 'id' → NO
33
+ 4. Internal only: metadata.to == 'internal' → NO
34
+ 5. Entity with id: 'id' in properties → YES
35
+ 6. Computed without id: description contains compute keywords AND no 'id' → NO
36
+ 7. Conservative default: metadata.to == 'external' AND has properties → YES
37
+ 8. Fallback: NO
38
+ """
39
+ try:
40
+ with open(contract_path, 'r') as f:
41
+ contract = json.load(f)
42
+
43
+ metadata = contract.get("x-artifact-metadata", {})
44
+ properties = contract.get("properties", {})
45
+ description = contract.get("description", "").lower()
46
+
47
+ # Extract aspect name from path
48
+ aspect = contract_path.stem.replace(".schema", "")
49
+
50
+ # Rule 1: Check persistence metadata
51
+ persistence = metadata.get("persistence", {})
52
+ if persistence.get("strategy") == "none":
53
+ return False
54
+ elif persistence.get("strategy") in ["jsonb", "relational"]:
55
+ return True
56
+
57
+ # Rule 2: Empty properties
58
+ if len(properties) == 0:
59
+ return False
60
+
61
+ # Rule 3: Event without id
62
+ has_id = "id" in properties
63
+ is_event_pattern = aspect.endswith("ed")
64
+ if is_event_pattern and not has_id:
65
+ return False
66
+
67
+ # Rule 4: Internal only
68
+ if metadata.get("to") == "internal":
69
+ return False
70
+
71
+ # Rule 5: Entity with id
72
+ if has_id:
73
+ return True
74
+
75
+ # Rule 6: Computed without id
76
+ compute_keywords = ["computed", "calculated", "derived", "aggregated", "aggregate"]
77
+ is_computed = any(keyword in description for keyword in compute_keywords)
78
+ if is_computed and not has_id:
79
+ return False
80
+
81
+ # Rule 7: Conservative default
82
+ if metadata.get("to") == "external" and len(properties) > 0:
83
+ return True
84
+
85
+ # Rule 8: Fallback
86
+ return False
87
+
88
+ except Exception as e:
89
+ print(f"Warning: Could not parse {contract_path}: {e}")
90
+ return True # Conservative: assume needs migration
91
+
92
+
93
+ def derive_table_name_from_contract(contract_path: Path) -> str:
94
+ """Derive table name from contract path: {theme}_{domain}_{aspect}"""
95
+ # Try relative to CONTRACTS_DIR first
96
+ try:
97
+ relative_path = contract_path.relative_to(CONTRACTS_DIR)
98
+ parts = relative_path.parts
99
+ except ValueError:
100
+ # Fallback: parse from path structure (for tests)
101
+ # Assume path is .../contracts/{theme}/{domain}/{aspect}.schema.json
102
+ parts = contract_path.parts
103
+ contracts_idx = parts.index("contracts") if "contracts" in parts else -4
104
+ parts = parts[contracts_idx + 1:]
105
+
106
+ theme = parts[0]
107
+ domain = parts[1]
108
+ aspect = contract_path.stem.replace(".schema", "")
109
+
110
+ return f"{theme}_{domain}_{aspect}".replace("-", "_")
111
+
112
+
113
+ def generate_migration_sql(contract_path: Path) -> str:
114
+ """
115
+ Generate standard JSONB blob table migration.
116
+
117
+ All tables use same structure:
118
+ - id UUID PRIMARY KEY
119
+ - data JSONB NOT NULL (stores entire contract)
120
+ - created_at, updated_at timestamps
121
+ - GIN index on data
122
+ - Table comment with contract $id for traceability
123
+ """
124
+ with open(contract_path, 'r') as f:
125
+ contract = json.load(f)
126
+
127
+ table_name = derive_table_name_from_contract(contract_path)
128
+ contract_id = contract.get("$id", "unknown")
129
+
130
+ # Standard JSONB table template
131
+ try:
132
+ path_display = str(contract_path.relative_to(REPO_ROOT))
133
+ except ValueError:
134
+ path_display = contract_path.name
135
+
136
+ sql = f"""-- Generated from {path_display}
137
+ -- Contract: {contract_id}
138
+ -- Generated: {datetime.now().isoformat()}
139
+ -- JSONB-first storage strategy (see migration.convention.yaml)
140
+
141
+ CREATE TABLE IF NOT EXISTS {table_name} (
142
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
143
+ data JSONB NOT NULL,
144
+ created_at TIMESTAMPTZ DEFAULT NOW(),
145
+ updated_at TIMESTAMPTZ DEFAULT NOW()
146
+ );
147
+
148
+ CREATE INDEX idx_{table_name}_data ON {table_name} USING GIN (data);
149
+
150
+ COMMENT ON TABLE {table_name} IS 'Contract: {contract_id}. JSONB blob storage for wagon architecture.';
151
+ """
152
+
153
+ return sql
154
+
155
+
156
+ def main():
157
+ parser = argparse.ArgumentParser(description="Generate Supabase JSONB migrations from contracts")
158
+ parser.add_argument("--contract", type=Path, help="Specific contract to generate migration for")
159
+ parser.add_argument("--validate", action="store_true", help="Only validate coverage, don't generate")
160
+
161
+ args = parser.parse_args()
162
+
163
+ MIGRATIONS_DIR.mkdir(parents=True, exist_ok=True)
164
+
165
+ if args.validate:
166
+ print("Validating migration coverage...")
167
+ print("Run: pytest atdd/tester/test_migration_coverage.py")
168
+ return
169
+
170
+ # Generate for specific contract
171
+ if args.contract:
172
+ if not args.contract.exists():
173
+ print(f"Error: Contract not found: {args.contract}")
174
+ return
175
+
176
+ if not contract_needs_migration(args.contract):
177
+ print(f"ā„¹ļø Contract does not need migration (strategy='none' or transient)")
178
+ return
179
+
180
+ migration_sql = generate_migration_sql(args.contract)
181
+ table_name = derive_table_name_from_contract(args.contract)
182
+
183
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
184
+ filename = f"{timestamp}_{table_name}.sql"
185
+ output_path = MIGRATIONS_DIR / filename
186
+
187
+ output_path.write_text(migration_sql)
188
+ print(f"āœ… Generated: {output_path.relative_to(REPO_ROOT)}")
189
+ print(f"šŸ“¦ JSONB blob storage - no manual review needed")
190
+ print(f"šŸš€ Apply: supabase db push")
191
+ return
192
+
193
+ # Generate all missing migrations
194
+ print("Scanning contracts for missing migrations...")
195
+
196
+ contracts = list(CONTRACTS_DIR.rglob("*.schema.json"))
197
+ generated = 0
198
+ skipped = 0
199
+
200
+ for contract in contracts:
201
+ relative_path = contract.relative_to(CONTRACTS_DIR)
202
+ parts = relative_path.parts
203
+
204
+ if len(parts) < 3:
205
+ continue
206
+
207
+ if not contract_needs_migration(contract):
208
+ skipped += 1
209
+ continue
210
+
211
+ table_name = derive_table_name_from_contract(contract)
212
+
213
+ # Check if migration exists
214
+ has_migration = any(
215
+ f"CREATE TABLE {table_name}" in mig.read_text() or
216
+ f"CREATE TABLE IF NOT EXISTS {table_name}" in mig.read_text()
217
+ for mig in MIGRATIONS_DIR.glob("*.sql")
218
+ )
219
+
220
+ if not has_migration:
221
+ migration_sql = generate_migration_sql(contract)
222
+
223
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
224
+ filename = f"{timestamp}_{table_name}.sql"
225
+ output_path = MIGRATIONS_DIR / filename
226
+
227
+ output_path.write_text(migration_sql)
228
+ print(f"āœ… Generated: {output_path.relative_to(REPO_ROOT)}")
229
+ generated += 1
230
+
231
+ print(f"\nāœ… Generated {generated} JSONB migrations")
232
+ if skipped > 0:
233
+ print(f"ā„¹ļø Skipped {skipped} non-persistent contracts")
234
+ print(f"šŸ“¦ All use standard JSONB blob storage")
235
+ print(f"šŸš€ Apply: supabase db push")
236
+ print(f"\nValidate: pytest atdd/tester/test_migration_coverage.py")
237
+
238
+
239
+ if __name__ == "__main__":
240
+ main()