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.
- atdd/__init__.py +6 -0
- atdd/__main__.py +4 -0
- atdd/cli.py +404 -0
- atdd/coach/__init__.py +0 -0
- atdd/coach/commands/__init__.py +0 -0
- atdd/coach/commands/add_persistence_metadata.py +215 -0
- atdd/coach/commands/analyze_migrations.py +188 -0
- atdd/coach/commands/consumers.py +720 -0
- atdd/coach/commands/infer_governance_status.py +149 -0
- atdd/coach/commands/initializer.py +177 -0
- atdd/coach/commands/interface.py +1078 -0
- atdd/coach/commands/inventory.py +565 -0
- atdd/coach/commands/migration.py +240 -0
- atdd/coach/commands/registry.py +1560 -0
- atdd/coach/commands/session.py +430 -0
- atdd/coach/commands/sync.py +405 -0
- atdd/coach/commands/test_interface.py +399 -0
- atdd/coach/commands/test_runner.py +141 -0
- atdd/coach/commands/tests/__init__.py +1 -0
- atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
- atdd/coach/commands/traceability.py +4264 -0
- atdd/coach/conventions/session.convention.yaml +754 -0
- atdd/coach/overlays/__init__.py +2 -0
- atdd/coach/overlays/claude.md +2 -0
- atdd/coach/schemas/config.schema.json +34 -0
- atdd/coach/schemas/manifest.schema.json +101 -0
- atdd/coach/templates/ATDD.md +282 -0
- atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
- atdd/coach/utils/__init__.py +0 -0
- atdd/coach/utils/graph/__init__.py +0 -0
- atdd/coach/utils/graph/urn.py +875 -0
- atdd/coach/validators/__init__.py +0 -0
- atdd/coach/validators/shared_fixtures.py +365 -0
- atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
- atdd/coach/validators/test_registry.py +575 -0
- atdd/coach/validators/test_session_validation.py +1183 -0
- atdd/coach/validators/test_traceability.py +448 -0
- atdd/coach/validators/test_update_feature_paths.py +108 -0
- atdd/coach/validators/test_validate_contract_consumers.py +297 -0
- atdd/coder/__init__.py +1 -0
- atdd/coder/conventions/adapter.recipe.yaml +88 -0
- atdd/coder/conventions/backend.convention.yaml +460 -0
- atdd/coder/conventions/boundaries.convention.yaml +666 -0
- atdd/coder/conventions/commons.convention.yaml +460 -0
- atdd/coder/conventions/complexity.recipe.yaml +109 -0
- atdd/coder/conventions/component-naming.convention.yaml +178 -0
- atdd/coder/conventions/design.convention.yaml +327 -0
- atdd/coder/conventions/design.recipe.yaml +273 -0
- atdd/coder/conventions/dto.convention.yaml +660 -0
- atdd/coder/conventions/frontend.convention.yaml +542 -0
- atdd/coder/conventions/green.convention.yaml +1012 -0
- atdd/coder/conventions/presentation.convention.yaml +587 -0
- atdd/coder/conventions/refactor.convention.yaml +535 -0
- atdd/coder/conventions/technology.convention.yaml +206 -0
- atdd/coder/conventions/tests/__init__.py +0 -0
- atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
- atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
- atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
- atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
- atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
- atdd/coder/conventions/thinness.recipe.yaml +82 -0
- atdd/coder/conventions/train.convention.yaml +325 -0
- atdd/coder/conventions/verification.protocol.yaml +53 -0
- atdd/coder/schemas/design_system.schema.json +361 -0
- atdd/coder/validators/__init__.py +0 -0
- atdd/coder/validators/test_commons_structure.py +485 -0
- atdd/coder/validators/test_complexity.py +416 -0
- atdd/coder/validators/test_cross_language_consistency.py +431 -0
- atdd/coder/validators/test_design_system_compliance.py +413 -0
- atdd/coder/validators/test_dto_testing_patterns.py +268 -0
- atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
- atdd/coder/validators/test_green_layer_dependencies.py +148 -0
- atdd/coder/validators/test_green_python_layer_structure.py +103 -0
- atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
- atdd/coder/validators/test_import_boundaries.py +396 -0
- atdd/coder/validators/test_init_file_urns.py +593 -0
- atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
- atdd/coder/validators/test_presentation_convention.py +260 -0
- atdd/coder/validators/test_python_architecture.py +674 -0
- atdd/coder/validators/test_quality_metrics.py +420 -0
- atdd/coder/validators/test_station_master_pattern.py +244 -0
- atdd/coder/validators/test_train_infrastructure.py +454 -0
- atdd/coder/validators/test_train_urns.py +293 -0
- atdd/coder/validators/test_typescript_architecture.py +616 -0
- atdd/coder/validators/test_usecase_structure.py +421 -0
- atdd/coder/validators/test_wagon_boundaries.py +586 -0
- atdd/conftest.py +126 -0
- atdd/planner/__init__.py +1 -0
- atdd/planner/conventions/acceptance.convention.yaml +538 -0
- atdd/planner/conventions/appendix.convention.yaml +187 -0
- atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
- atdd/planner/conventions/component.convention.yaml +670 -0
- atdd/planner/conventions/criteria.convention.yaml +141 -0
- atdd/planner/conventions/feature.convention.yaml +371 -0
- atdd/planner/conventions/interface.convention.yaml +382 -0
- atdd/planner/conventions/steps.convention.yaml +141 -0
- atdd/planner/conventions/train.convention.yaml +552 -0
- atdd/planner/conventions/wagon.convention.yaml +275 -0
- atdd/planner/conventions/wmbt.convention.yaml +258 -0
- atdd/planner/schemas/acceptance.schema.json +336 -0
- atdd/planner/schemas/appendix.schema.json +78 -0
- atdd/planner/schemas/component.schema.json +114 -0
- atdd/planner/schemas/feature.schema.json +197 -0
- atdd/planner/schemas/train.schema.json +192 -0
- atdd/planner/schemas/wagon.schema.json +281 -0
- atdd/planner/schemas/wmbt.schema.json +59 -0
- atdd/planner/validators/__init__.py +0 -0
- atdd/planner/validators/conftest.py +5 -0
- atdd/planner/validators/test_draft_wagon_registry.py +374 -0
- atdd/planner/validators/test_plan_cross_refs.py +240 -0
- atdd/planner/validators/test_plan_uniqueness.py +224 -0
- atdd/planner/validators/test_plan_urn_resolution.py +268 -0
- atdd/planner/validators/test_plan_wagons.py +174 -0
- atdd/planner/validators/test_train_validation.py +514 -0
- atdd/planner/validators/test_wagon_urn_chain.py +648 -0
- atdd/planner/validators/test_wmbt_consistency.py +327 -0
- atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
- atdd/tester/__init__.py +1 -0
- atdd/tester/conventions/artifact.convention.yaml +257 -0
- atdd/tester/conventions/contract.convention.yaml +1009 -0
- atdd/tester/conventions/filename.convention.yaml +555 -0
- atdd/tester/conventions/migration.convention.yaml +509 -0
- atdd/tester/conventions/red.convention.yaml +797 -0
- atdd/tester/conventions/routing.convention.yaml +51 -0
- atdd/tester/conventions/telemetry.convention.yaml +458 -0
- atdd/tester/schemas/a11y.tmpl.json +17 -0
- atdd/tester/schemas/artifact.schema.json +189 -0
- atdd/tester/schemas/contract.schema.json +591 -0
- atdd/tester/schemas/contract.tmpl.json +95 -0
- atdd/tester/schemas/db.tmpl.json +20 -0
- atdd/tester/schemas/e2e.tmpl.json +17 -0
- atdd/tester/schemas/edge_function.tmpl.json +17 -0
- atdd/tester/schemas/event.tmpl.json +17 -0
- atdd/tester/schemas/http.tmpl.json +19 -0
- atdd/tester/schemas/job.tmpl.json +18 -0
- atdd/tester/schemas/load.tmpl.json +21 -0
- atdd/tester/schemas/metric.tmpl.json +19 -0
- atdd/tester/schemas/pack.schema.json +139 -0
- atdd/tester/schemas/realtime.tmpl.json +20 -0
- atdd/tester/schemas/rls.tmpl.json +18 -0
- atdd/tester/schemas/script.tmpl.json +16 -0
- atdd/tester/schemas/sec.tmpl.json +18 -0
- atdd/tester/schemas/storage.tmpl.json +18 -0
- atdd/tester/schemas/telemetry.schema.json +128 -0
- atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
- atdd/tester/schemas/test_filename.schema.json +194 -0
- atdd/tester/schemas/test_intent.schema.json +179 -0
- atdd/tester/schemas/unit.tmpl.json +18 -0
- atdd/tester/schemas/visual.tmpl.json +18 -0
- atdd/tester/schemas/ws.tmpl.json +17 -0
- atdd/tester/utils/__init__.py +0 -0
- atdd/tester/utils/filename.py +300 -0
- atdd/tester/validators/__init__.py +0 -0
- atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
- atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
- atdd/tester/validators/conftest.py +5 -0
- atdd/tester/validators/coverage_gap_report.py +321 -0
- atdd/tester/validators/fix_dual_ac_references.py +179 -0
- atdd/tester/validators/remove_duplicate_lines.py +93 -0
- atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
- atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
- atdd/tester/validators/test_artifact_naming_category.py +307 -0
- atdd/tester/validators/test_contract_schema_compliance.py +706 -0
- atdd/tester/validators/test_contracts_structure.py +200 -0
- atdd/tester/validators/test_coverage_adequacy.py +797 -0
- atdd/tester/validators/test_dual_ac_reference.py +225 -0
- atdd/tester/validators/test_fixture_validity.py +372 -0
- atdd/tester/validators/test_isolation.py +487 -0
- atdd/tester/validators/test_migration_coverage.py +204 -0
- atdd/tester/validators/test_migration_criteria.py +276 -0
- atdd/tester/validators/test_migration_generation.py +116 -0
- atdd/tester/validators/test_python_test_naming.py +410 -0
- atdd/tester/validators/test_red_layer_validation.py +95 -0
- atdd/tester/validators/test_red_python_layer_structure.py +87 -0
- atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
- atdd/tester/validators/test_telemetry_structure.py +634 -0
- atdd/tester/validators/test_typescript_test_naming.py +301 -0
- atdd/tester/validators/test_typescript_test_structure.py +84 -0
- atdd-0.2.1.dist-info/METADATA +221 -0
- atdd-0.2.1.dist-info/RECORD +184 -0
- atdd-0.2.1.dist-info/WHEEL +5 -0
- atdd-0.2.1.dist-info/entry_points.txt +2 -0
- atdd-0.2.1.dist-info/licenses/LICENSE +674 -0
- atdd-0.2.1.dist-info/top_level.txt +1 -0
atdd/__init__.py
ADDED
atdd/__main__.py
ADDED
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()
|