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
atdd/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("atdd")
5
+ except PackageNotFoundError:
6
+ __version__ = "0.0.0"
atdd/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
atdd/cli.py ADDED
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ATDD Platform - Unified command-line interface.
4
+
5
+ The coach orchestrates all ATDD lifecycle operations:
6
+ - Inventory: Catalog repository artifacts
7
+ - Test: Run meta-tests (planner/tester/coder)
8
+ - Report: Generate test reports
9
+ - Validate: Validate artifacts against conventions
10
+ - Init: Initialize ATDD structure in consumer repos
11
+ - Session: Manage session files
12
+ - Sync: Sync ATDD rules to agent config files
13
+
14
+ Usage:
15
+ atdd init # Initialize ATDD in consumer repo
16
+ atdd session new my-feature # Create new session
17
+ atdd session list # List all sessions
18
+ atdd session archive 01 # Archive session
19
+ atdd sync # Sync ATDD rules to agent configs
20
+ atdd sync --verify # Check if files are in sync
21
+ atdd sync --agent claude # Sync specific agent only
22
+ atdd --inventory # Generate inventory
23
+ atdd --test all # Run all meta-tests
24
+ atdd --test planner # Run planner phase tests
25
+ atdd --test tester # Run tester phase tests
26
+ atdd --test coder # Run coder phase tests
27
+ atdd --test all --coverage # With coverage report
28
+ atdd --test all --html # With HTML report
29
+ atdd --help # Show help
30
+ """
31
+
32
+ import argparse
33
+ import sys
34
+ from pathlib import Path
35
+
36
+ ATDD_DIR = Path(__file__).parent
37
+
38
+ from atdd.coach.commands.inventory import RepositoryInventory
39
+ from atdd.coach.commands.test_runner import TestRunner
40
+ from atdd.coach.commands.registry import RegistryUpdater
41
+ from atdd.coach.commands.initializer import ProjectInitializer
42
+ from atdd.coach.commands.session import SessionManager
43
+ from atdd.coach.commands.sync import AgentConfigSync
44
+
45
+
46
+ class ATDDCoach:
47
+ """
48
+ ATDD Platform Coach - orchestrates all operations.
49
+
50
+ The coach role coordinates across the three ATDD phases:
51
+ - Planner: Planning phase validation
52
+ - Tester: Testing phase validation (contracts-as-code)
53
+ - Coder: Implementation phase validation
54
+ """
55
+
56
+ def __init__(self):
57
+ self.repo_root = ATDD_DIR.parent
58
+ self.inventory = RepositoryInventory(self.repo_root)
59
+ self.test_runner = TestRunner(self.repo_root)
60
+ self.registry_updater = RegistryUpdater(self.repo_root)
61
+
62
+ def run_inventory(self, format: str = "yaml") -> int:
63
+ """Generate repository inventory."""
64
+ print("📊 Generating repository inventory...")
65
+ data = self.inventory.generate()
66
+
67
+ if format == "json":
68
+ import json
69
+ print(json.dumps(data, indent=2))
70
+ else:
71
+ import yaml
72
+ print("\n" + "=" * 60)
73
+ print("Repository Inventory")
74
+ print("=" * 60 + "\n")
75
+ print(yaml.dump(data, default_flow_style=False, sort_keys=False))
76
+
77
+ return 0
78
+
79
+ def run_tests(
80
+ self,
81
+ phase: str = "all",
82
+ verbose: bool = False,
83
+ coverage: bool = False,
84
+ html: bool = False,
85
+ quick: bool = False
86
+ ) -> int:
87
+ """Run ATDD meta-tests."""
88
+ if quick:
89
+ return self.test_runner.quick_check()
90
+
91
+ return self.test_runner.run_tests(
92
+ phase=phase,
93
+ verbose=verbose,
94
+ coverage=coverage,
95
+ html_report=html,
96
+ parallel=True
97
+ )
98
+
99
+ def update_registries(self, registry_type: str = "all") -> int:
100
+ """Update registries from source files."""
101
+ if registry_type == "wagons":
102
+ self.registry_updater.update_wagon_registry()
103
+ elif registry_type == "contracts":
104
+ self.registry_updater.update_contract_registry()
105
+ elif registry_type == "telemetry":
106
+ self.registry_updater.update_telemetry_registry()
107
+ else: # all
108
+ self.registry_updater.update_all()
109
+ return 0
110
+
111
+ def show_status(self) -> int:
112
+ """Show quick status summary."""
113
+ print("=" * 60)
114
+ print("ATDD Platform Status")
115
+ print("=" * 60)
116
+ print("\nDirectory structure:")
117
+ print(f" 📋 Planner tests: {ATDD_DIR / 'planner'}")
118
+ print(f" 🧪 Tester tests: {ATDD_DIR / 'tester'}")
119
+ print(f" ⚙️ Coder tests: {ATDD_DIR / 'coder'}")
120
+ print(f" 🎯 Coach: {ATDD_DIR / 'coach'}")
121
+
122
+ # Quick stats
123
+ planner_tests = len(list((ATDD_DIR / "planner").glob("test_*.py")))
124
+ tester_tests = len(list((ATDD_DIR / "tester").glob("test_*.py")))
125
+ coder_tests = len(list((ATDD_DIR / "coder").glob("test_*.py")))
126
+
127
+ print(f"\nTest files:")
128
+ print(f" Planner: {planner_tests} files")
129
+ print(f" Tester: {tester_tests} files")
130
+ print(f" Coder: {coder_tests} files")
131
+ print(f" Total: {planner_tests + tester_tests + coder_tests} files")
132
+
133
+ return 0
134
+
135
+
136
+ def main():
137
+ """Main CLI entry point."""
138
+ parser = argparse.ArgumentParser(
139
+ description="ATDD Platform - Coach orchestrates all ATDD operations",
140
+ formatter_class=argparse.RawDescriptionHelpFormatter,
141
+ epilog="""
142
+ Examples:
143
+ # Initialize ATDD in consumer repo
144
+ %(prog)s init Create atdd-sessions/, .atdd/
145
+ %(prog)s init --force Overwrite if exists
146
+
147
+ # Session management
148
+ %(prog)s session new my-feature Create SESSION-NN-my-feature.md
149
+ %(prog)s session new my-feature --type migration
150
+ %(prog)s session list List all sessions
151
+ %(prog)s session archive 01 Archive SESSION-01-*.md
152
+
153
+ # Agent config sync
154
+ %(prog)s sync Sync ATDD rules to agent configs
155
+ %(prog)s sync --verify Check if files are in sync (CI)
156
+ %(prog)s sync --agent claude Sync specific agent only
157
+ %(prog)s sync --status Show sync status
158
+
159
+ # Existing flag-based commands (backwards compatible)
160
+ %(prog)s --inventory Generate full inventory (YAML)
161
+ %(prog)s --inventory --format json Generate inventory (JSON)
162
+ %(prog)s --test all Run all meta-tests
163
+ %(prog)s --test planner Run planner phase tests
164
+ %(prog)s --test tester Run tester phase tests
165
+ %(prog)s --test coder Run coder phase tests
166
+ %(prog)s --test all --coverage Run with coverage report
167
+ %(prog)s --test all --html Run with HTML report
168
+ %(prog)s --test all --verbose Run with verbose output
169
+ %(prog)s --quick Quick smoke test
170
+ %(prog)s --status Show platform status
171
+
172
+ Phase descriptions:
173
+ planner - Validates planning artifacts (wagons, trains, URNs)
174
+ tester - Validates testing artifacts (contracts, telemetry)
175
+ coder - Validates implementation (architecture, quality)
176
+ """
177
+ )
178
+
179
+ # Subparsers for new commands
180
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
181
+
182
+ # ----- atdd init -----
183
+ init_parser = subparsers.add_parser(
184
+ "init",
185
+ help="Initialize ATDD structure in consumer repo",
186
+ description="Create atdd-sessions/ and .atdd/ directories with manifest"
187
+ )
188
+ init_parser.add_argument(
189
+ "--force", "-f",
190
+ action="store_true",
191
+ help="Overwrite existing files"
192
+ )
193
+
194
+ # ----- atdd session {new,list,archive,sync} -----
195
+ session_parser = subparsers.add_parser(
196
+ "session",
197
+ help="Manage session files",
198
+ description="Create, list, and archive session files"
199
+ )
200
+ session_subparsers = session_parser.add_subparsers(
201
+ dest="session_command",
202
+ help="Session commands"
203
+ )
204
+
205
+ # atdd session new <slug>
206
+ new_parser = session_subparsers.add_parser(
207
+ "new",
208
+ help="Create new session from template",
209
+ description="Create a new session file with next available number"
210
+ )
211
+ new_parser.add_argument(
212
+ "slug",
213
+ type=str,
214
+ help="Session name (will be converted to kebab-case)"
215
+ )
216
+ new_parser.add_argument(
217
+ "--type", "-t",
218
+ type=str,
219
+ default="implementation",
220
+ choices=["implementation", "migration", "refactor", "analysis", "planning", "cleanup", "tracking"],
221
+ help="Session type (default: implementation)"
222
+ )
223
+
224
+ # atdd session list
225
+ session_subparsers.add_parser(
226
+ "list",
227
+ help="List all sessions from manifest"
228
+ )
229
+
230
+ # atdd session archive <session_id>
231
+ archive_parser = session_subparsers.add_parser(
232
+ "archive",
233
+ help="Move session to archive/",
234
+ description="Archive a completed session"
235
+ )
236
+ archive_parser.add_argument(
237
+ "session_id",
238
+ type=str,
239
+ help="Session ID to archive (e.g., '01' or '1')"
240
+ )
241
+
242
+ # atdd session sync
243
+ session_subparsers.add_parser(
244
+ "sync",
245
+ help="Sync manifest with actual session files"
246
+ )
247
+
248
+ # ----- atdd sync -----
249
+ sync_parser = subparsers.add_parser(
250
+ "sync",
251
+ help="Sync ATDD rules to agent config files",
252
+ description="Sync managed ATDD blocks to agent config files (CLAUDE.md, AGENTS.md, etc.)"
253
+ )
254
+ sync_parser.add_argument(
255
+ "--verify",
256
+ action="store_true",
257
+ help="Check if files are in sync (for CI)"
258
+ )
259
+ sync_parser.add_argument(
260
+ "--agent",
261
+ type=str,
262
+ choices=["claude", "codex", "gemini", "qwen"],
263
+ help="Sync specific agent only"
264
+ )
265
+ sync_parser.add_argument(
266
+ "--status",
267
+ action="store_true",
268
+ help="Show sync status for all agents"
269
+ )
270
+
271
+ # ----- Existing flag-based arguments (backwards compatible) -----
272
+
273
+ # Main command groups
274
+ parser.add_argument(
275
+ "--inventory",
276
+ action="store_true",
277
+ help="Generate repository inventory"
278
+ )
279
+
280
+ parser.add_argument(
281
+ "--test",
282
+ type=str,
283
+ choices=["all", "planner", "tester", "coder"],
284
+ metavar="PHASE",
285
+ help="Run tests for specific phase (all, planner, tester, coder)"
286
+ )
287
+
288
+ parser.add_argument(
289
+ "--status",
290
+ action="store_true",
291
+ help="Show platform status summary"
292
+ )
293
+
294
+ parser.add_argument(
295
+ "--quick",
296
+ action="store_true",
297
+ help="Quick smoke test (no parallel, no reports)"
298
+ )
299
+
300
+ parser.add_argument(
301
+ "--update-registry",
302
+ type=str,
303
+ choices=["all", "wagons", "contracts", "telemetry"],
304
+ metavar="TYPE",
305
+ help="Update registry from source files (all, wagons, contracts, telemetry)"
306
+ )
307
+
308
+ # Options for inventory
309
+ parser.add_argument(
310
+ "--format",
311
+ type=str,
312
+ choices=["yaml", "json"],
313
+ default="yaml",
314
+ help="Inventory output format (default: yaml)"
315
+ )
316
+
317
+ # Options for tests
318
+ parser.add_argument(
319
+ "--verbose", "-v",
320
+ action="store_true",
321
+ help="Verbose test output"
322
+ )
323
+
324
+ parser.add_argument(
325
+ "--coverage",
326
+ action="store_true",
327
+ help="Generate coverage report"
328
+ )
329
+
330
+ parser.add_argument(
331
+ "--html",
332
+ action="store_true",
333
+ help="Generate HTML test report"
334
+ )
335
+
336
+ args = parser.parse_args()
337
+
338
+ # ----- Handle subcommands -----
339
+
340
+ # atdd init
341
+ if args.command == "init":
342
+ initializer = ProjectInitializer()
343
+ return initializer.init(force=args.force)
344
+
345
+ # atdd session {new,list,archive,sync}
346
+ elif args.command == "session":
347
+ manager = SessionManager()
348
+
349
+ if args.session_command == "new":
350
+ return manager.new(slug=args.slug, session_type=args.type)
351
+ elif args.session_command == "list":
352
+ return manager.list()
353
+ elif args.session_command == "archive":
354
+ return manager.archive(session_id=args.session_id)
355
+ elif args.session_command == "sync":
356
+ return manager.sync()
357
+ else:
358
+ session_parser.print_help()
359
+ return 0
360
+
361
+ # atdd sync
362
+ elif args.command == "sync":
363
+ syncer = AgentConfigSync()
364
+ if args.status:
365
+ return syncer.status()
366
+ if args.verify:
367
+ return syncer.verify()
368
+ return syncer.sync(agents=[args.agent] if args.agent else None)
369
+
370
+ # ----- Handle flag-based commands (backwards compatible) -----
371
+
372
+ # Create coach instance
373
+ coach = ATDDCoach()
374
+
375
+ # Handle commands
376
+ if args.inventory:
377
+ return coach.run_inventory(format=args.format)
378
+
379
+ elif args.test:
380
+ return coach.run_tests(
381
+ phase=args.test,
382
+ verbose=args.verbose,
383
+ coverage=args.coverage,
384
+ html=args.html,
385
+ quick=False
386
+ )
387
+
388
+ elif args.quick:
389
+ return coach.run_tests(quick=True)
390
+
391
+ elif args.status:
392
+ return coach.show_status()
393
+
394
+ elif args.update_registry:
395
+ return coach.update_registries(registry_type=args.update_registry)
396
+
397
+ else:
398
+ # No command specified - show help
399
+ parser.print_help()
400
+ return 0
401
+
402
+
403
+ if __name__ == "__main__":
404
+ sys.exit(main())
atdd/coach/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Add persistence metadata to contracts based on existing migrations.
4
+
5
+ Links contracts to database tables for bidirectional traceability.
6
+ """
7
+
8
+ import json
9
+ import re
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional
12
+
13
+ REPO_ROOT = Path(__file__).resolve().parents[4]
14
+ CONTRACTS_DIR = REPO_ROOT / "contracts"
15
+ MIGRATIONS_DIR = REPO_ROOT / "supabase" / "migrations"
16
+
17
+
18
+ def find_migration_for_table(table_name: str) -> Optional[str]:
19
+ """Find migration file that created a specific table."""
20
+ for migration_file in MIGRATIONS_DIR.glob("*.sql"):
21
+ content = migration_file.read_text()
22
+ if f"CREATE TABLE IF NOT EXISTS {table_name}" in content or \
23
+ f"CREATE TABLE {table_name}" in content:
24
+ return f"supabase/migrations/{migration_file.name}"
25
+ return None
26
+
27
+
28
+ def extract_indexes_from_migration(migration_path: Path, table_name: str) -> List[Dict]:
29
+ """Extract index definitions from migration file."""
30
+ indexes = []
31
+ content = migration_path.read_text()
32
+
33
+ # Match: CREATE INDEX idx_name ON table_name ...
34
+ index_pattern = rf"CREATE INDEX (\w+) ON {table_name}(?: USING (\w+))?\s*\((.*?)\)"
35
+
36
+ for match in re.finditer(index_pattern, content, re.MULTILINE):
37
+ index_name = match.group(1)
38
+ index_type = match.group(2).lower() if match.group(2) else "btree"
39
+ fields_raw = match.group(3)
40
+
41
+ # Parse fields (handle JSONB -> notation)
42
+ fields = [f.strip() for f in fields_raw.split(",")]
43
+
44
+ indexes.append({
45
+ "name": index_name,
46
+ "type": index_type,
47
+ "fields": fields
48
+ })
49
+
50
+ return indexes
51
+
52
+
53
+ def add_persistence_to_contract(contract_path: Path, table_name: str, migration_path: str, indexes: List[Dict]) -> bool:
54
+ """Add persistence metadata to a contract file."""
55
+ try:
56
+ with open(contract_path) as f:
57
+ contract = json.load(f)
58
+
59
+ metadata = contract.get("x-artifact-metadata", {})
60
+
61
+ # Check if already has persistence
62
+ if "persistence" in metadata:
63
+ print(f" ⚠️ {contract_path.name} already has persistence metadata")
64
+ return False
65
+
66
+ # Add persistence metadata
67
+ metadata["persistence"] = {
68
+ "strategy": "jsonb",
69
+ "table": table_name,
70
+ "migration": migration_path,
71
+ "indexes": indexes
72
+ }
73
+
74
+ contract["x-artifact-metadata"] = metadata
75
+
76
+ # Write back with pretty formatting
77
+ with open(contract_path, 'w') as f:
78
+ json.dump(contract, f, indent=2)
79
+ f.write('\n') # Add trailing newline
80
+
81
+ return True
82
+
83
+ except Exception as e:
84
+ print(f" ❌ Error updating {contract_path.name}: {e}")
85
+ return False
86
+
87
+
88
+ def contract_id_to_path(contract_id: str) -> Optional[Path]:
89
+ """Convert contract $id to file path.
90
+
91
+ Examples:
92
+ commons:ux:themes:skin → contracts/commons/ux/themes/skin.schema.json
93
+ match:dilemma:current → contracts/match/dilemma/current.schema.json
94
+ """
95
+ parts = contract_id.split(":")
96
+ if len(parts) < 2:
97
+ return None
98
+
99
+ # Build path: contracts/{theme}/{path...}/{aspect}.schema.json
100
+ path = CONTRACTS_DIR / "/".join(parts[:-1]) / f"{parts[-1]}.schema.json"
101
+
102
+ return path if path.exists() else None
103
+
104
+
105
+ def table_name_to_contract_id(table_name: str) -> str:
106
+ """Convert table name back to contract $id.
107
+
108
+ Examples:
109
+ commons_ux_skin → commons:ux:themes:skin (if that contract exists)
110
+ match_dilemma_current → match:dilemma:current
111
+ """
112
+ # Try different path depths
113
+ parts = table_name.split("_")
114
+
115
+ # Try as-is first: match_dilemma_current → match:dilemma:current
116
+ contract_id = ":".join(parts)
117
+ path = contract_id_to_path(contract_id)
118
+ if path:
119
+ return contract_id
120
+
121
+ # For commons_ux_skin, we need to find the actual contract
122
+ # Check if it's a nested structure by trying different combinations
123
+ for i in range(1, len(parts)):
124
+ theme = parts[0]
125
+ mid_parts = parts[1:i+1]
126
+ aspect = parts[i+1] if i+1 < len(parts) else parts[-1]
127
+
128
+ # Try: theme:mid1:mid2:...:aspect
129
+ test_id = f"{theme}:{':'.join(mid_parts)}:{aspect}"
130
+ path = contract_id_to_path(test_id)
131
+ if path:
132
+ return test_id
133
+
134
+ # Fallback to simple conversion
135
+ return ":".join(parts)
136
+
137
+
138
+ def main():
139
+ """Add persistence metadata to all contracts with existing migrations."""
140
+ print("=" * 80)
141
+ print("Add Persistence Metadata to Contracts")
142
+ print("=" * 80)
143
+ print()
144
+
145
+ # Find all tables in migrations
146
+ tables_found = {}
147
+
148
+ for migration_file in MIGRATIONS_DIR.glob("*.sql"):
149
+ content = migration_file.read_text()
150
+
151
+ # Find all CREATE TABLE statements
152
+ pattern = r"CREATE TABLE IF NOT EXISTS (\w+)|CREATE TABLE (\w+)"
153
+ for match in re.finditer(pattern, content):
154
+ table_name = match.group(1) or match.group(2)
155
+ if table_name and table_name not in ["information_schema", "pg_catalog"]:
156
+ tables_found[table_name] = migration_file
157
+
158
+ print(f"Found {len(tables_found)} tables in migrations:\n")
159
+
160
+ updated = 0
161
+ skipped = 0
162
+ errors = 0
163
+
164
+ for table_name, migration_file in tables_found.items():
165
+ print(f"📋 Processing table: {table_name}")
166
+
167
+ # Convert table name to contract ID
168
+ contract_id = table_name_to_contract_id(table_name)
169
+ contract_path = contract_id_to_path(contract_id)
170
+
171
+ if not contract_path:
172
+ print(f" ⚠️ No contract found for table {table_name}")
173
+ errors += 1
174
+ continue
175
+
176
+ print(f" Contract: {contract_path.relative_to(REPO_ROOT)}")
177
+
178
+ # Get migration path
179
+ migration_rel_path = f"supabase/migrations/{migration_file.name}"
180
+
181
+ # Extract indexes
182
+ indexes = extract_indexes_from_migration(migration_file, table_name)
183
+ print(f" Indexes: {len(indexes)}")
184
+
185
+ # Add persistence metadata
186
+ if add_persistence_to_contract(contract_path, table_name, migration_rel_path, indexes):
187
+ print(f" ✅ Added persistence metadata")
188
+ updated += 1
189
+ else:
190
+ skipped += 1
191
+
192
+ print()
193
+
194
+ # Summary
195
+ print("=" * 80)
196
+ print("SUMMARY")
197
+ print("=" * 80)
198
+ print(f" Tables found: {len(tables_found)}")
199
+ print(f" ✅ Updated: {updated}")
200
+ print(f" ⏭️ Skipped: {skipped}")
201
+ print(f" ❌ Errors: {errors}")
202
+ print()
203
+
204
+ if updated > 0:
205
+ print("✅ Persistence metadata added to contracts!")
206
+ print(" Contracts now link to their database tables.")
207
+ print()
208
+ print("Next steps:")
209
+ print(" 1. Review the updated contracts")
210
+ print(" 2. Commit the changes")
211
+ print(" 3. Run validation: pytest tests/platform_validation/")
212
+
213
+
214
+ if __name__ == "__main__":
215
+ main()