atdd 0.4.5__py3-none-any.whl → 0.4.6__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/cli.py +71 -12
- atdd/coach/commands/registry.py +366 -202
- {atdd-0.4.5.dist-info → atdd-0.4.6.dist-info}/METADATA +1 -1
- {atdd-0.4.5.dist-info → atdd-0.4.6.dist-info}/RECORD +8 -8
- {atdd-0.4.5.dist-info → atdd-0.4.6.dist-info}/WHEEL +0 -0
- {atdd-0.4.5.dist-info → atdd-0.4.6.dist-info}/entry_points.txt +0 -0
- {atdd-0.4.5.dist-info → atdd-0.4.6.dist-info}/licenses/LICENSE +0 -0
- {atdd-0.4.5.dist-info → atdd-0.4.6.dist-info}/top_level.txt +0 -0
atdd/cli.py
CHANGED
|
@@ -110,16 +110,60 @@ class ATDDCoach:
|
|
|
110
110
|
parallel=True
|
|
111
111
|
)
|
|
112
112
|
|
|
113
|
-
def update_registries(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
def update_registries(
|
|
114
|
+
self,
|
|
115
|
+
registry_type: str = "all",
|
|
116
|
+
apply: bool = False,
|
|
117
|
+
check: bool = False
|
|
118
|
+
) -> int:
|
|
119
|
+
"""Update registries from source files.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
registry_type: Which registry to update (all, wagons, trains, contracts, etc.)
|
|
123
|
+
apply: If True, apply changes without prompting (CI mode)
|
|
124
|
+
check: If True, only check for drift without applying (exit 1 if drift)
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
0 on success, 1 if --check and drift detected
|
|
128
|
+
"""
|
|
129
|
+
# Convert flags to mode string
|
|
130
|
+
if check:
|
|
131
|
+
mode = "check"
|
|
132
|
+
elif apply:
|
|
133
|
+
mode = "apply"
|
|
134
|
+
else:
|
|
135
|
+
mode = "interactive"
|
|
136
|
+
|
|
137
|
+
# Registry type handlers
|
|
138
|
+
handlers = {
|
|
139
|
+
"wagons": self.registry_updater.update_wagon_registry,
|
|
140
|
+
"trains": self.registry_updater.build_trains,
|
|
141
|
+
"contracts": self.registry_updater.update_contract_registry,
|
|
142
|
+
"telemetry": self.registry_updater.update_telemetry_registry,
|
|
143
|
+
"tester": self.registry_updater.build_tester,
|
|
144
|
+
"coder": self.registry_updater.build_coder,
|
|
145
|
+
"supabase": self.registry_updater.build_supabase,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if registry_type == "all":
|
|
149
|
+
result = self.registry_updater.build_all(mode=mode)
|
|
150
|
+
# In check mode, return 1 if any registry has changes
|
|
151
|
+
if check:
|
|
152
|
+
has_changes = any(
|
|
153
|
+
r.get("has_changes", False) or r.get("new", 0) > 0 or len(r.get("changes", [])) > 0
|
|
154
|
+
for r in result.values()
|
|
155
|
+
)
|
|
156
|
+
return 1 if has_changes else 0
|
|
157
|
+
elif registry_type in handlers:
|
|
158
|
+
result = handlers[registry_type](mode=mode)
|
|
159
|
+
# In check mode, return 1 if this registry has changes
|
|
160
|
+
if check:
|
|
161
|
+
has_changes = result.get("has_changes", False) or result.get("new", 0) > 0 or len(result.get("changes", [])) > 0
|
|
162
|
+
return 1 if has_changes else 0
|
|
163
|
+
else:
|
|
164
|
+
print(f"Unknown registry type: {registry_type}")
|
|
165
|
+
return 1
|
|
166
|
+
|
|
123
167
|
return 0
|
|
124
168
|
|
|
125
169
|
def show_status(self) -> int:
|
|
@@ -285,9 +329,20 @@ Phase descriptions:
|
|
|
285
329
|
nargs="?",
|
|
286
330
|
type=str,
|
|
287
331
|
default="all",
|
|
288
|
-
choices=["all", "wagons", "contracts", "telemetry"],
|
|
332
|
+
choices=["all", "wagons", "trains", "contracts", "telemetry", "tester", "coder", "supabase"],
|
|
289
333
|
help="Registry type to update (default: all)"
|
|
290
334
|
)
|
|
335
|
+
registry_update_parser.add_argument(
|
|
336
|
+
"--yes", "--apply",
|
|
337
|
+
action="store_true",
|
|
338
|
+
dest="apply",
|
|
339
|
+
help="Apply changes without prompting (for CI/automation)"
|
|
340
|
+
)
|
|
341
|
+
registry_update_parser.add_argument(
|
|
342
|
+
"--check",
|
|
343
|
+
action="store_true",
|
|
344
|
+
help="Check for drift without applying (exit 1 if changes detected)"
|
|
345
|
+
)
|
|
291
346
|
|
|
292
347
|
# ----- atdd init -----
|
|
293
348
|
init_parser = subparsers.add_parser(
|
|
@@ -497,7 +552,11 @@ Phase descriptions:
|
|
|
497
552
|
coach = ATDDCoach(repo_root=repo_path)
|
|
498
553
|
|
|
499
554
|
if args.registry_command == "update":
|
|
500
|
-
return coach.update_registries(
|
|
555
|
+
return coach.update_registries(
|
|
556
|
+
registry_type=args.type,
|
|
557
|
+
apply=args.apply,
|
|
558
|
+
check=args.check
|
|
559
|
+
)
|
|
501
560
|
else:
|
|
502
561
|
registry_parser.print_help()
|
|
503
562
|
return 0
|
atdd/coach/commands/registry.py
CHANGED
|
@@ -160,6 +160,74 @@ class RegistryBuilder:
|
|
|
160
160
|
self.python_dir = repo_root / "python"
|
|
161
161
|
self.supabase_dir = repo_root / "supabase"
|
|
162
162
|
|
|
163
|
+
# ========================================================================
|
|
164
|
+
# MODE HANDLING - Unified confirmation and apply logic
|
|
165
|
+
# ========================================================================
|
|
166
|
+
# Handles interactive, apply, and check modes for all registries
|
|
167
|
+
# ========================================================================
|
|
168
|
+
|
|
169
|
+
def _confirm_and_apply(
|
|
170
|
+
self,
|
|
171
|
+
mode: str,
|
|
172
|
+
registry_name: str,
|
|
173
|
+
registry_path: Path,
|
|
174
|
+
output_data: Dict[str, Any],
|
|
175
|
+
stats: Dict[str, Any],
|
|
176
|
+
preview_msg: str = ""
|
|
177
|
+
) -> Dict[str, Any]:
|
|
178
|
+
"""
|
|
179
|
+
Handle confirmation and apply based on mode.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
mode: "interactive", "apply", or "check"
|
|
183
|
+
registry_name: Human-readable name for messages (e.g., "wagon", "contract")
|
|
184
|
+
registry_path: Path to the registry file
|
|
185
|
+
output_data: Data to write to the registry
|
|
186
|
+
stats: Statistics dict to update with results
|
|
187
|
+
preview_msg: Optional custom preview message
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Updated stats dict with has_changes flag
|
|
191
|
+
"""
|
|
192
|
+
has_changes = stats.get("new", 0) > 0 or len(stats.get("changes", [])) > 0
|
|
193
|
+
stats["has_changes"] = has_changes
|
|
194
|
+
|
|
195
|
+
if mode == "check":
|
|
196
|
+
if has_changes:
|
|
197
|
+
print(f"\n⚠️ Drift detected in {registry_name} registry")
|
|
198
|
+
else:
|
|
199
|
+
print(f"\n✅ {registry_name.capitalize()} registry is in sync")
|
|
200
|
+
return stats
|
|
201
|
+
|
|
202
|
+
if mode == "apply":
|
|
203
|
+
# Write without prompting
|
|
204
|
+
registry_path.parent.mkdir(parents=True, exist_ok=True)
|
|
205
|
+
with open(registry_path, "w") as f:
|
|
206
|
+
yaml.dump(output_data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
207
|
+
|
|
208
|
+
print(f"\n✅ {registry_name.capitalize()} registry updated successfully!")
|
|
209
|
+
print(f" 📝 Registry: {registry_path}")
|
|
210
|
+
return stats
|
|
211
|
+
|
|
212
|
+
# Interactive mode - ask for confirmation
|
|
213
|
+
print(f"\n❓ Do you want to apply these changes to the {registry_name} registry?")
|
|
214
|
+
print(" Type 'yes' to confirm, or anything else to cancel:")
|
|
215
|
+
response = input(" > ").strip().lower()
|
|
216
|
+
|
|
217
|
+
if response != "yes":
|
|
218
|
+
print("\n❌ Update cancelled by user")
|
|
219
|
+
stats["cancelled"] = True
|
|
220
|
+
return stats
|
|
221
|
+
|
|
222
|
+
# Write registry
|
|
223
|
+
registry_path.parent.mkdir(parents=True, exist_ok=True)
|
|
224
|
+
with open(registry_path, "w") as f:
|
|
225
|
+
yaml.dump(output_data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
226
|
+
|
|
227
|
+
print(f"\n✅ {registry_name.capitalize()} registry updated successfully!")
|
|
228
|
+
print(f" 📝 Registry: {registry_path}")
|
|
229
|
+
return stats
|
|
230
|
+
|
|
163
231
|
# ========================================================================
|
|
164
232
|
# DOMAIN LAYER - Pure Business Logic (Change Detection)
|
|
165
233
|
# ========================================================================
|
|
@@ -469,16 +537,20 @@ class RegistryBuilder:
|
|
|
469
537
|
# Reads/writes YAML files, scans directories for source files
|
|
470
538
|
# ========================================================================
|
|
471
539
|
|
|
472
|
-
def update_wagon_registry(self, preview_only: bool =
|
|
540
|
+
def update_wagon_registry(self, mode: str = "interactive", preview_only: bool = None) -> Dict[str, Any]:
|
|
473
541
|
"""
|
|
474
542
|
Update plan/_wagons.yaml from wagon manifest files.
|
|
475
543
|
|
|
476
544
|
Args:
|
|
477
|
-
|
|
545
|
+
mode: "interactive" (prompt), "apply" (no prompt), or "check" (verify only)
|
|
546
|
+
preview_only: Deprecated - use mode="check" instead
|
|
478
547
|
|
|
479
548
|
Returns:
|
|
480
|
-
Statistics about the update
|
|
549
|
+
Statistics about the update (includes has_changes flag for check mode)
|
|
481
550
|
"""
|
|
551
|
+
# Backwards compatibility
|
|
552
|
+
if preview_only is not None:
|
|
553
|
+
mode = "check" if preview_only else "interactive"
|
|
482
554
|
print("📊 Analyzing wagon registry from manifest files...")
|
|
483
555
|
|
|
484
556
|
# Load existing registry
|
|
@@ -500,7 +572,7 @@ class RegistryBuilder:
|
|
|
500
572
|
"updated": 0,
|
|
501
573
|
"new": 0,
|
|
502
574
|
"preserved_drafts": 0,
|
|
503
|
-
"changes": []
|
|
575
|
+
"changes": []
|
|
504
576
|
}
|
|
505
577
|
|
|
506
578
|
for manifest_path in sorted(manifest_files):
|
|
@@ -539,7 +611,6 @@ class RegistryBuilder:
|
|
|
539
611
|
# Check if updating or new
|
|
540
612
|
if slug in existing_wagons:
|
|
541
613
|
stats["updated"] += 1
|
|
542
|
-
# Track field-level changes
|
|
543
614
|
changes = self._detect_changes(slug, existing_wagons[slug], entry)
|
|
544
615
|
if changes:
|
|
545
616
|
stats["changes"].append({
|
|
@@ -560,13 +631,17 @@ class RegistryBuilder:
|
|
|
560
631
|
except Exception as e:
|
|
561
632
|
print(f" ❌ Error processing {manifest_path}: {e}")
|
|
562
633
|
|
|
563
|
-
# Preserve draft wagons (those without manifests)
|
|
634
|
+
# Preserve draft wagons (those without manifests or with draft: true)
|
|
564
635
|
preserved_drafts = []
|
|
565
636
|
for slug, wagon in existing_wagons.items():
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
637
|
+
is_draft = wagon.get("draft", False)
|
|
638
|
+
has_no_manifest = not wagon.get("manifest") and not wagon.get("path")
|
|
639
|
+
if is_draft or has_no_manifest:
|
|
640
|
+
# Check if already added from manifest scan
|
|
641
|
+
if slug not in [w.get("wagon") for w in updated_wagons]:
|
|
642
|
+
updated_wagons.append(wagon)
|
|
643
|
+
preserved_drafts.append(slug)
|
|
644
|
+
stats["preserved_drafts"] += 1
|
|
570
645
|
|
|
571
646
|
# Sort by wagon slug
|
|
572
647
|
updated_wagons.sort(key=lambda w: w.get("wagon", ""))
|
|
@@ -580,44 +655,24 @@ class RegistryBuilder:
|
|
|
580
655
|
# Print detailed change report
|
|
581
656
|
self._print_change_report(stats["changes"], preserved_drafts)
|
|
582
657
|
|
|
583
|
-
#
|
|
584
|
-
if preview_only:
|
|
585
|
-
print("\n⚠️ Preview mode - no changes applied")
|
|
586
|
-
return stats
|
|
587
|
-
|
|
588
|
-
# Ask for user approval
|
|
589
|
-
print("\n❓ Do you want to apply these changes to the registry?")
|
|
590
|
-
print(" Type 'yes' to confirm, or anything else to cancel:")
|
|
591
|
-
response = input(" > ").strip().lower()
|
|
592
|
-
|
|
593
|
-
if response != "yes":
|
|
594
|
-
print("\n❌ Update cancelled by user")
|
|
595
|
-
stats["cancelled"] = True
|
|
596
|
-
return stats
|
|
597
|
-
|
|
598
|
-
# Write updated registry
|
|
658
|
+
# Use helper for confirm/apply
|
|
599
659
|
output = {"wagons": updated_wagons}
|
|
600
|
-
|
|
601
|
-
yaml.dump(output, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
602
|
-
|
|
603
|
-
print(f"\n✅ Registry updated successfully!")
|
|
604
|
-
print(f" • Updated {stats['updated']} wagons")
|
|
605
|
-
print(f" • Added {stats['new']} new wagons")
|
|
606
|
-
print(f" • Preserved {stats['preserved_drafts']} draft wagons")
|
|
607
|
-
print(f" 📝 Registry: {registry_path}")
|
|
608
|
-
|
|
609
|
-
return stats
|
|
660
|
+
return self._confirm_and_apply(mode, "wagon", registry_path, output, stats)
|
|
610
661
|
|
|
611
|
-
def update_contract_registry(self, preview_only: bool =
|
|
662
|
+
def update_contract_registry(self, mode: str = "interactive", preview_only: bool = None) -> Dict[str, Any]:
|
|
612
663
|
"""
|
|
613
664
|
Update contracts/_artifacts.yaml from contract schema files.
|
|
614
665
|
|
|
615
666
|
Args:
|
|
616
|
-
|
|
667
|
+
mode: "interactive" (prompt), "apply" (no prompt), or "check" (verify only)
|
|
668
|
+
preview_only: Deprecated - use mode="check" instead
|
|
617
669
|
|
|
618
670
|
Returns:
|
|
619
|
-
Statistics about the update
|
|
671
|
+
Statistics about the update (includes has_changes flag for check mode)
|
|
620
672
|
"""
|
|
673
|
+
# Backwards compatibility
|
|
674
|
+
if preview_only is not None:
|
|
675
|
+
mode = "check" if preview_only else "interactive"
|
|
621
676
|
print("\n📊 Analyzing contract registry from schema files...")
|
|
622
677
|
|
|
623
678
|
# Load existing registry
|
|
@@ -635,6 +690,7 @@ class RegistryBuilder:
|
|
|
635
690
|
"updated": 0,
|
|
636
691
|
"new": 0,
|
|
637
692
|
"errors": 0,
|
|
693
|
+
"preserved_drafts": 0,
|
|
638
694
|
"changes": []
|
|
639
695
|
}
|
|
640
696
|
|
|
@@ -657,7 +713,7 @@ class RegistryBuilder:
|
|
|
657
713
|
# Build artifact entry
|
|
658
714
|
rel_path = str(schema_path.relative_to(self.repo_root))
|
|
659
715
|
|
|
660
|
-
artifact_id = schema_id
|
|
716
|
+
artifact_id = schema_id
|
|
661
717
|
artifact = {
|
|
662
718
|
"id": artifact_id,
|
|
663
719
|
"urn": f"contract:{schema_id}",
|
|
@@ -694,55 +750,44 @@ class RegistryBuilder:
|
|
|
694
750
|
print(f" ⚠️ Error processing {schema_path}: {e}")
|
|
695
751
|
stats["errors"] += 1
|
|
696
752
|
|
|
753
|
+
# Preserve draft artifacts (path doesn't exist or draft: true)
|
|
754
|
+
for artifact_id, artifact in existing_artifacts.items():
|
|
755
|
+
is_draft = artifact.get("draft", False)
|
|
756
|
+
path_exists = artifact.get("path") and (self.repo_root / artifact.get("path")).exists()
|
|
757
|
+
if is_draft or not path_exists:
|
|
758
|
+
if artifact_id not in [a.get("id") for a in artifacts]:
|
|
759
|
+
artifacts.append(artifact)
|
|
760
|
+
stats["preserved_drafts"] += 1
|
|
761
|
+
|
|
697
762
|
# Show preview
|
|
698
763
|
print(f"\n📋 PREVIEW:")
|
|
699
764
|
print(f" • {stats['updated']} artifacts will be updated")
|
|
700
765
|
print(f" • {stats['new']} new artifacts will be added")
|
|
766
|
+
print(f" • {stats['preserved_drafts']} draft artifacts will be preserved")
|
|
701
767
|
if stats["errors"] > 0:
|
|
702
768
|
print(f" ⚠️ {stats['errors']} errors encountered")
|
|
703
769
|
|
|
704
770
|
# Print detailed change report
|
|
705
771
|
self._print_contract_change_report(stats["changes"])
|
|
706
772
|
|
|
707
|
-
#
|
|
708
|
-
if preview_only:
|
|
709
|
-
print("\n⚠️ Preview mode - no changes applied")
|
|
710
|
-
return stats
|
|
711
|
-
|
|
712
|
-
# Ask for user approval
|
|
713
|
-
print("\n❓ Do you want to apply these changes to the contract registry?")
|
|
714
|
-
print(" Type 'yes' to confirm, or anything else to cancel:")
|
|
715
|
-
response = input(" > ").strip().lower()
|
|
716
|
-
|
|
717
|
-
if response != "yes":
|
|
718
|
-
print("\n❌ Update cancelled by user")
|
|
719
|
-
stats["cancelled"] = True
|
|
720
|
-
return stats
|
|
721
|
-
|
|
722
|
-
# Write registry
|
|
723
|
-
registry_path = self.contracts_dir / "_artifacts.yaml"
|
|
773
|
+
# Use helper for confirm/apply
|
|
724
774
|
output = {"artifacts": artifacts}
|
|
775
|
+
return self._confirm_and_apply(mode, "contract", registry_path, output, stats)
|
|
725
776
|
|
|
726
|
-
|
|
727
|
-
yaml.dump(output, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
728
|
-
|
|
729
|
-
print(f"\n✅ Contract registry updated successfully!")
|
|
730
|
-
print(f" • Updated {stats['updated']} artifacts")
|
|
731
|
-
print(f" • Added {stats['new']} new artifacts")
|
|
732
|
-
print(f" 📝 Registry: {registry_path}")
|
|
733
|
-
|
|
734
|
-
return stats
|
|
735
|
-
|
|
736
|
-
def update_telemetry_registry(self, preview_only: bool = False) -> Dict[str, Any]:
|
|
777
|
+
def update_telemetry_registry(self, mode: str = "interactive", preview_only: bool = None) -> Dict[str, Any]:
|
|
737
778
|
"""
|
|
738
779
|
Update telemetry/_signals.yaml from telemetry signal files.
|
|
739
780
|
|
|
740
781
|
Args:
|
|
741
|
-
|
|
782
|
+
mode: "interactive" (prompt), "apply" (no prompt), or "check" (verify only)
|
|
783
|
+
preview_only: Deprecated - use mode="check" instead
|
|
742
784
|
|
|
743
785
|
Returns:
|
|
744
|
-
Statistics about the update
|
|
786
|
+
Statistics about the update (includes has_changes flag for check mode)
|
|
745
787
|
"""
|
|
788
|
+
# Backwards compatibility
|
|
789
|
+
if preview_only is not None:
|
|
790
|
+
mode = "check" if preview_only else "interactive"
|
|
746
791
|
print("\n📊 Analyzing telemetry registry from signal files...")
|
|
747
792
|
|
|
748
793
|
# Load existing registry
|
|
@@ -760,6 +805,7 @@ class RegistryBuilder:
|
|
|
760
805
|
"updated": 0,
|
|
761
806
|
"new": 0,
|
|
762
807
|
"errors": 0,
|
|
808
|
+
"preserved_drafts": 0,
|
|
763
809
|
"changes": []
|
|
764
810
|
}
|
|
765
811
|
|
|
@@ -820,63 +866,211 @@ class RegistryBuilder:
|
|
|
820
866
|
print(f" ⚠️ Error processing {signal_path}: {e}")
|
|
821
867
|
stats["errors"] += 1
|
|
822
868
|
|
|
869
|
+
# Preserve draft signals (path doesn't exist or draft: true)
|
|
870
|
+
for signal_id, signal in existing_signals.items():
|
|
871
|
+
is_draft = signal.get("draft", False)
|
|
872
|
+
path_exists = signal.get("path") and (self.repo_root / signal.get("path")).exists()
|
|
873
|
+
if is_draft or not path_exists:
|
|
874
|
+
if signal_id not in [s.get("id") for s in signals]:
|
|
875
|
+
signals.append(signal)
|
|
876
|
+
stats["preserved_drafts"] += 1
|
|
877
|
+
|
|
823
878
|
# Show preview
|
|
824
879
|
print(f"\n📋 PREVIEW:")
|
|
825
880
|
print(f" • {stats['updated']} signals will be updated")
|
|
826
881
|
print(f" • {stats['new']} new signals will be added")
|
|
882
|
+
print(f" • {stats['preserved_drafts']} draft signals will be preserved")
|
|
827
883
|
if stats["errors"] > 0:
|
|
828
884
|
print(f" ⚠️ {stats['errors']} errors encountered")
|
|
829
885
|
|
|
830
886
|
# Print detailed change report
|
|
831
887
|
self._print_telemetry_change_report(stats["changes"])
|
|
832
888
|
|
|
833
|
-
#
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
return stats
|
|
889
|
+
# Use helper for confirm/apply
|
|
890
|
+
output = {"signals": signals}
|
|
891
|
+
return self._confirm_and_apply(mode, "telemetry", registry_path, output, stats)
|
|
837
892
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
893
|
+
# Alias methods for unified API
|
|
894
|
+
def build_planner(self, mode: str = "interactive", preview_only: bool = None) -> Dict[str, Any]:
|
|
895
|
+
"""Build planner registry (alias for update_wagon_registry)."""
|
|
896
|
+
# Backwards compatibility: preview_only=True maps to mode="check"
|
|
897
|
+
if preview_only is not None:
|
|
898
|
+
mode = "check" if preview_only else "interactive"
|
|
899
|
+
return self.update_wagon_registry(mode)
|
|
842
900
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
901
|
+
def build_contracts(self, mode: str = "interactive", preview_only: bool = None) -> Dict[str, Any]:
|
|
902
|
+
"""Build contracts registry (alias for update_contract_registry)."""
|
|
903
|
+
if preview_only is not None:
|
|
904
|
+
mode = "check" if preview_only else "interactive"
|
|
905
|
+
return self.update_contract_registry(mode)
|
|
906
|
+
|
|
907
|
+
def build_telemetry(self, mode: str = "interactive", preview_only: bool = None) -> Dict[str, Any]:
|
|
908
|
+
"""Build telemetry registry (alias for update_telemetry_registry)."""
|
|
909
|
+
if preview_only is not None:
|
|
910
|
+
mode = "check" if preview_only else "interactive"
|
|
911
|
+
return self.update_telemetry_registry(mode)
|
|
912
|
+
|
|
913
|
+
def build_trains(self, mode: str = "interactive") -> Dict[str, Any]:
|
|
914
|
+
"""
|
|
915
|
+
Build trains registry from train manifest files.
|
|
916
|
+
Scans plan/_trains/*.yaml files and builds plan/_trains.yaml.
|
|
917
|
+
|
|
918
|
+
Train ID convention: NN-XX-name where:
|
|
919
|
+
- NN = theme prefix (first 2 digits for grouping)
|
|
920
|
+
- XX = category within theme
|
|
921
|
+
- name = train slug
|
|
922
|
+
|
|
923
|
+
Args:
|
|
924
|
+
mode: "interactive" (prompt), "apply" (no prompt), or "check" (verify only)
|
|
925
|
+
|
|
926
|
+
Returns:
|
|
927
|
+
Statistics about the update (includes has_changes flag for check mode)
|
|
928
|
+
"""
|
|
929
|
+
print("\n📊 Analyzing trains registry from manifest files...")
|
|
930
|
+
|
|
931
|
+
# Set up paths
|
|
932
|
+
trains_dir = self.plan_dir / "_trains"
|
|
933
|
+
registry_path = self.plan_dir / "_trains.yaml"
|
|
934
|
+
|
|
935
|
+
# Load existing registry
|
|
936
|
+
existing_trains = {}
|
|
937
|
+
if registry_path.exists():
|
|
938
|
+
with open(registry_path) as f:
|
|
939
|
+
registry_data = yaml.safe_load(f)
|
|
940
|
+
existing_trains = {t.get("train_id"): t for t in registry_data.get("trains", [])}
|
|
941
|
+
|
|
942
|
+
trains = []
|
|
943
|
+
stats = {
|
|
944
|
+
"total_manifests": 0,
|
|
945
|
+
"processed": 0,
|
|
946
|
+
"updated": 0,
|
|
947
|
+
"new": 0,
|
|
948
|
+
"errors": 0,
|
|
949
|
+
"preserved_drafts": 0,
|
|
950
|
+
"changes": []
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
# Check if trains directory exists
|
|
954
|
+
if not trains_dir.exists():
|
|
955
|
+
print(f" ⚠️ No _trains/ directory found at {trains_dir}")
|
|
956
|
+
# Still preserve existing drafts
|
|
957
|
+
for train_id, train in existing_trains.items():
|
|
958
|
+
if train.get("draft", False):
|
|
959
|
+
trains.append(train)
|
|
960
|
+
stats["preserved_drafts"] += 1
|
|
961
|
+
|
|
962
|
+
if stats["preserved_drafts"] > 0:
|
|
963
|
+
print(f"\n📋 PREVIEW:")
|
|
964
|
+
print(f" • {stats['preserved_drafts']} draft trains will be preserved")
|
|
965
|
+
output = {"trains": trains}
|
|
966
|
+
return self._confirm_and_apply(mode, "trains", registry_path, output, stats)
|
|
967
|
+
|
|
968
|
+
stats["has_changes"] = False
|
|
846
969
|
return stats
|
|
847
970
|
|
|
848
|
-
#
|
|
849
|
-
|
|
850
|
-
|
|
971
|
+
# Scan for train manifests
|
|
972
|
+
manifest_files = list(trains_dir.glob("*.yaml"))
|
|
973
|
+
manifest_files = [f for f in manifest_files if not f.name.startswith("_")]
|
|
974
|
+
stats["total_manifests"] = len(manifest_files)
|
|
851
975
|
|
|
852
|
-
|
|
853
|
-
|
|
976
|
+
for manifest_path in sorted(manifest_files):
|
|
977
|
+
try:
|
|
978
|
+
with open(manifest_path) as f:
|
|
979
|
+
manifest = yaml.safe_load(f)
|
|
854
980
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
print(f" 📝 Registry: {registry_path}")
|
|
981
|
+
if not manifest:
|
|
982
|
+
print(f" ⚠️ Skipping empty manifest: {manifest_path}")
|
|
983
|
+
continue
|
|
859
984
|
|
|
860
|
-
|
|
985
|
+
train_id = manifest.get("train_id", manifest.get("train", ""))
|
|
986
|
+
if not train_id:
|
|
987
|
+
# Try to infer from filename (e.g., 01-01-setup.yaml -> 01-01-setup)
|
|
988
|
+
train_id = manifest_path.stem
|
|
861
989
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
990
|
+
# Parse theme from train_id (first 2 digits)
|
|
991
|
+
theme = ""
|
|
992
|
+
if len(train_id) >= 2 and train_id[:2].isdigit():
|
|
993
|
+
theme = train_id[:2]
|
|
866
994
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
return self.update_contract_registry(preview_only)
|
|
995
|
+
# Build train entry
|
|
996
|
+
rel_manifest = str(manifest_path.relative_to(self.repo_root))
|
|
870
997
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
998
|
+
entry = {
|
|
999
|
+
"train_id": train_id,
|
|
1000
|
+
"theme": theme,
|
|
1001
|
+
"title": manifest.get("title", manifest.get("description", "")),
|
|
1002
|
+
"description": manifest.get("description", ""),
|
|
1003
|
+
"wagons": manifest.get("wagons", []),
|
|
1004
|
+
"status": manifest.get("status", "planned"),
|
|
1005
|
+
"manifest": rel_manifest
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
# Check if updating or new
|
|
1009
|
+
if train_id in existing_trains:
|
|
1010
|
+
stats["updated"] += 1
|
|
1011
|
+
# Check for field changes
|
|
1012
|
+
old = existing_trains[train_id]
|
|
1013
|
+
changed_fields = []
|
|
1014
|
+
for field in ["title", "description", "wagons", "status", "theme"]:
|
|
1015
|
+
if old.get(field) != entry.get(field):
|
|
1016
|
+
changed_fields.append(field)
|
|
1017
|
+
if changed_fields:
|
|
1018
|
+
stats["changes"].append({
|
|
1019
|
+
"train": train_id,
|
|
1020
|
+
"type": "updated",
|
|
1021
|
+
"fields": changed_fields
|
|
1022
|
+
})
|
|
1023
|
+
else:
|
|
1024
|
+
stats["new"] += 1
|
|
1025
|
+
stats["changes"].append({
|
|
1026
|
+
"train": train_id,
|
|
1027
|
+
"type": "new",
|
|
1028
|
+
"fields": ["all fields (new train)"]
|
|
1029
|
+
})
|
|
1030
|
+
|
|
1031
|
+
trains.append(entry)
|
|
1032
|
+
stats["processed"] += 1
|
|
874
1033
|
|
|
875
|
-
|
|
1034
|
+
except Exception as e:
|
|
1035
|
+
print(f" ❌ Error processing {manifest_path}: {e}")
|
|
1036
|
+
stats["errors"] += 1
|
|
1037
|
+
|
|
1038
|
+
# Preserve draft trains (those without manifests or with draft: true)
|
|
1039
|
+
for train_id, train in existing_trains.items():
|
|
1040
|
+
is_draft = train.get("draft", False)
|
|
1041
|
+
has_no_manifest = not train.get("manifest")
|
|
1042
|
+
if is_draft or has_no_manifest:
|
|
1043
|
+
if train_id not in [t.get("train_id") for t in trains]:
|
|
1044
|
+
trains.append(train)
|
|
1045
|
+
stats["preserved_drafts"] += 1
|
|
1046
|
+
|
|
1047
|
+
# Sort by train_id
|
|
1048
|
+
trains.sort(key=lambda t: t.get("train_id", ""))
|
|
1049
|
+
|
|
1050
|
+
# Show preview
|
|
1051
|
+
print(f"\n📋 PREVIEW:")
|
|
1052
|
+
print(f" • {stats['updated']} trains will be updated")
|
|
1053
|
+
print(f" • {stats['new']} new trains will be added")
|
|
1054
|
+
print(f" • {stats['preserved_drafts']} draft trains will be preserved")
|
|
1055
|
+
if stats["errors"] > 0:
|
|
1056
|
+
print(f" ⚠️ {stats['errors']} errors encountered")
|
|
1057
|
+
|
|
1058
|
+
# Use helper for confirm/apply
|
|
1059
|
+
output = {"trains": trains}
|
|
1060
|
+
return self._confirm_and_apply(mode, "trains", registry_path, output, stats)
|
|
1061
|
+
|
|
1062
|
+
def build_tester(self, mode: str = "interactive", preview_only: bool = None) -> Dict[str, Any]:
|
|
876
1063
|
"""
|
|
877
1064
|
Build tester registry from test files.
|
|
878
1065
|
Scans atdd/tester/**/*_test.py files for URNs and metadata.
|
|
1066
|
+
|
|
1067
|
+
Args:
|
|
1068
|
+
mode: "interactive" (prompt), "apply" (no prompt), or "check" (verify only)
|
|
1069
|
+
preview_only: Deprecated - use mode="check" instead
|
|
879
1070
|
"""
|
|
1071
|
+
# Backwards compatibility
|
|
1072
|
+
if preview_only is not None:
|
|
1073
|
+
mode = "check" if preview_only else "interactive"
|
|
880
1074
|
print("\n📊 Analyzing tester registry from test files...")
|
|
881
1075
|
|
|
882
1076
|
# Load existing registry
|
|
@@ -894,12 +1088,12 @@ class RegistryBuilder:
|
|
|
894
1088
|
"updated": 0,
|
|
895
1089
|
"new": 0,
|
|
896
1090
|
"errors": 0,
|
|
1091
|
+
"preserved_drafts": 0,
|
|
897
1092
|
"changes": []
|
|
898
1093
|
}
|
|
899
1094
|
|
|
900
1095
|
# Scan for test files
|
|
901
1096
|
if self.tester_dir.exists():
|
|
902
|
-
# Look for both test_*.py and *_test.py patterns
|
|
903
1097
|
test_files = list(self.tester_dir.glob("**/*_test.py"))
|
|
904
1098
|
test_files.extend(list(self.tester_dir.glob("**/test_*.py")))
|
|
905
1099
|
test_files = [f for f in test_files if not f.name.startswith("_")]
|
|
@@ -910,16 +1104,13 @@ class RegistryBuilder:
|
|
|
910
1104
|
with open(test_file) as f:
|
|
911
1105
|
content = f.read()
|
|
912
1106
|
|
|
913
|
-
# Extract URN markers from docstring or comments
|
|
914
1107
|
urns = re.findall(r'URN:\s*(\S+)', content)
|
|
915
1108
|
spec_urns = re.findall(r'Spec:\s*(\S+)', content)
|
|
916
1109
|
acceptance_urns = re.findall(r'Acceptance:\s*(\S+)', content)
|
|
917
1110
|
|
|
918
|
-
# Extract wagon from path
|
|
919
1111
|
rel_path = test_file.relative_to(self.tester_dir)
|
|
920
1112
|
wagon = rel_path.parts[0] if len(rel_path.parts) > 1 else "unknown"
|
|
921
1113
|
|
|
922
|
-
# Build test entry
|
|
923
1114
|
for urn in urns:
|
|
924
1115
|
test_entry = {
|
|
925
1116
|
"urn": urn,
|
|
@@ -932,7 +1123,6 @@ class RegistryBuilder:
|
|
|
932
1123
|
if acceptance_urns:
|
|
933
1124
|
test_entry["acceptance_urn"] = acceptance_urns[0]
|
|
934
1125
|
|
|
935
|
-
# Track changes
|
|
936
1126
|
if urn in existing_tests:
|
|
937
1127
|
stats["updated"] += 1
|
|
938
1128
|
else:
|
|
@@ -950,45 +1140,39 @@ class RegistryBuilder:
|
|
|
950
1140
|
print(f" ⚠️ Error processing {test_file}: {e}")
|
|
951
1141
|
stats["errors"] += 1
|
|
952
1142
|
|
|
1143
|
+
# Preserve draft tests (file doesn't exist or draft: true)
|
|
1144
|
+
for urn, test in existing_tests.items():
|
|
1145
|
+
is_draft = test.get("draft", False)
|
|
1146
|
+
file_exists = test.get("file") and (self.repo_root / test.get("file")).exists()
|
|
1147
|
+
if is_draft or not file_exists:
|
|
1148
|
+
if urn not in [t.get("urn") for t in tests]:
|
|
1149
|
+
tests.append(test)
|
|
1150
|
+
stats["preserved_drafts"] += 1
|
|
1151
|
+
|
|
953
1152
|
# Show preview
|
|
954
1153
|
print(f"\n📋 PREVIEW:")
|
|
955
1154
|
print(f" • {stats['updated']} tests will be updated")
|
|
956
1155
|
print(f" • {stats['new']} new tests will be added")
|
|
1156
|
+
print(f" • {stats['preserved_drafts']} draft tests will be preserved")
|
|
957
1157
|
if stats["errors"] > 0:
|
|
958
1158
|
print(f" ⚠️ {stats['errors']} errors encountered")
|
|
959
1159
|
|
|
960
|
-
|
|
961
|
-
print("\n⚠️ Preview mode - no changes applied")
|
|
962
|
-
return stats
|
|
963
|
-
|
|
964
|
-
# Ask for confirmation
|
|
965
|
-
print("\n❓ Do you want to apply these changes to the tester registry?")
|
|
966
|
-
print(" Type 'yes' to confirm, or anything else to cancel:")
|
|
967
|
-
response = input(" > ").strip().lower()
|
|
968
|
-
|
|
969
|
-
if response != "yes":
|
|
970
|
-
print("\n❌ Update cancelled by user")
|
|
971
|
-
stats["cancelled"] = True
|
|
972
|
-
return stats
|
|
973
|
-
|
|
974
|
-
# Write registry
|
|
1160
|
+
# Use helper for confirm/apply
|
|
975
1161
|
output = {"tests": tests}
|
|
976
|
-
|
|
977
|
-
with open(registry_path, "w") as f:
|
|
978
|
-
yaml.dump(output, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
979
|
-
|
|
980
|
-
print(f"\n✅ Tester registry updated successfully!")
|
|
981
|
-
print(f" • Updated {stats['updated']} tests")
|
|
982
|
-
print(f" • Added {stats['new']} new tests")
|
|
983
|
-
print(f" 📝 Registry: {registry_path}")
|
|
984
|
-
|
|
985
|
-
return stats
|
|
1162
|
+
return self._confirm_and_apply(mode, "tester", registry_path, output, stats)
|
|
986
1163
|
|
|
987
|
-
def build_coder(self, preview_only: bool =
|
|
1164
|
+
def build_coder(self, mode: str = "interactive", preview_only: bool = None) -> Dict[str, Any]:
|
|
988
1165
|
"""
|
|
989
1166
|
Build coder implementation registry from Python files.
|
|
990
1167
|
Scans python/**/*.py files for implementations.
|
|
1168
|
+
|
|
1169
|
+
Args:
|
|
1170
|
+
mode: "interactive" (prompt), "apply" (no prompt), or "check" (verify only)
|
|
1171
|
+
preview_only: Deprecated - use mode="check" instead
|
|
991
1172
|
"""
|
|
1173
|
+
# Backwards compatibility
|
|
1174
|
+
if preview_only is not None:
|
|
1175
|
+
mode = "check" if preview_only else "interactive"
|
|
992
1176
|
print("\n📊 Analyzing coder registry from Python files...")
|
|
993
1177
|
|
|
994
1178
|
# Load existing registry
|
|
@@ -1006,13 +1190,13 @@ class RegistryBuilder:
|
|
|
1006
1190
|
"updated": 0,
|
|
1007
1191
|
"new": 0,
|
|
1008
1192
|
"errors": 0,
|
|
1193
|
+
"preserved_drafts": 0,
|
|
1009
1194
|
"changes": []
|
|
1010
1195
|
}
|
|
1011
1196
|
|
|
1012
1197
|
# Scan for Python implementation files
|
|
1013
1198
|
if self.python_dir.exists():
|
|
1014
1199
|
py_files = list(self.python_dir.glob("**/*.py"))
|
|
1015
|
-
# Filter out __init__, __pycache__, and files in specific test directories
|
|
1016
1200
|
py_files = [
|
|
1017
1201
|
f for f in py_files
|
|
1018
1202
|
if not f.name.startswith("_")
|
|
@@ -1029,18 +1213,15 @@ class RegistryBuilder:
|
|
|
1029
1213
|
with open(py_file) as f:
|
|
1030
1214
|
content = f.read()
|
|
1031
1215
|
|
|
1032
|
-
# Extract metadata from docstring
|
|
1033
1216
|
spec_urns = re.findall(r'Spec:\s*(\S+)', content)
|
|
1034
1217
|
test_urns = re.findall(r'Test:\s*(\S+)', content)
|
|
1035
1218
|
|
|
1036
|
-
# Extract wagon and layer from path
|
|
1037
1219
|
rel_path = py_file.relative_to(self.python_dir)
|
|
1038
1220
|
parts = rel_path.parts
|
|
1039
1221
|
|
|
1040
1222
|
wagon = parts[0] if len(parts) > 0 else "unknown"
|
|
1041
1223
|
layer = "unknown"
|
|
1042
1224
|
|
|
1043
|
-
# Try to detect layer from path
|
|
1044
1225
|
if "domain" in str(py_file):
|
|
1045
1226
|
layer = "domain"
|
|
1046
1227
|
elif "application" in str(py_file):
|
|
@@ -1050,17 +1231,15 @@ class RegistryBuilder:
|
|
|
1050
1231
|
elif "presentation" in str(py_file):
|
|
1051
1232
|
layer = "presentation"
|
|
1052
1233
|
|
|
1053
|
-
# Generate URN
|
|
1054
1234
|
component = py_file.stem
|
|
1055
1235
|
impl_urn = f"impl:{wagon}:{layer}:{component}:python"
|
|
1056
1236
|
|
|
1057
|
-
# Build implementation entry
|
|
1058
1237
|
impl_entry = {
|
|
1059
1238
|
"urn": impl_urn,
|
|
1060
1239
|
"file": str(py_file.relative_to(self.repo_root)),
|
|
1061
1240
|
"wagon": wagon,
|
|
1062
1241
|
"layer": layer,
|
|
1063
|
-
"component_type": "entity",
|
|
1242
|
+
"component_type": "entity",
|
|
1064
1243
|
"language": "python"
|
|
1065
1244
|
}
|
|
1066
1245
|
|
|
@@ -1069,7 +1248,6 @@ class RegistryBuilder:
|
|
|
1069
1248
|
if test_urns:
|
|
1070
1249
|
impl_entry["test_urn"] = test_urns[0]
|
|
1071
1250
|
|
|
1072
|
-
# Track changes
|
|
1073
1251
|
if impl_urn in existing_impls:
|
|
1074
1252
|
stats["updated"] += 1
|
|
1075
1253
|
else:
|
|
@@ -1087,45 +1265,39 @@ class RegistryBuilder:
|
|
|
1087
1265
|
print(f" ⚠️ Error processing {py_file}: {e}")
|
|
1088
1266
|
stats["errors"] += 1
|
|
1089
1267
|
|
|
1268
|
+
# Preserve draft implementations (file doesn't exist or draft: true)
|
|
1269
|
+
for urn, impl in existing_impls.items():
|
|
1270
|
+
is_draft = impl.get("draft", False)
|
|
1271
|
+
file_exists = impl.get("file") and (self.repo_root / impl.get("file")).exists()
|
|
1272
|
+
if is_draft or not file_exists:
|
|
1273
|
+
if urn not in [i.get("urn") for i in implementations]:
|
|
1274
|
+
implementations.append(impl)
|
|
1275
|
+
stats["preserved_drafts"] += 1
|
|
1276
|
+
|
|
1090
1277
|
# Show preview
|
|
1091
1278
|
print(f"\n📋 PREVIEW:")
|
|
1092
1279
|
print(f" • {stats['updated']} implementations will be updated")
|
|
1093
1280
|
print(f" • {stats['new']} new implementations will be added")
|
|
1281
|
+
print(f" • {stats['preserved_drafts']} draft implementations will be preserved")
|
|
1094
1282
|
if stats["errors"] > 0:
|
|
1095
1283
|
print(f" ⚠️ {stats['errors']} errors encountered")
|
|
1096
1284
|
|
|
1097
|
-
|
|
1098
|
-
print("\n⚠️ Preview mode - no changes applied")
|
|
1099
|
-
return stats
|
|
1100
|
-
|
|
1101
|
-
# Ask for confirmation
|
|
1102
|
-
print("\n❓ Do you want to apply these changes to the coder registry?")
|
|
1103
|
-
print(" Type 'yes' to confirm, or anything else to cancel:")
|
|
1104
|
-
response = input(" > ").strip().lower()
|
|
1105
|
-
|
|
1106
|
-
if response != "yes":
|
|
1107
|
-
print("\n❌ Update cancelled by user")
|
|
1108
|
-
stats["cancelled"] = True
|
|
1109
|
-
return stats
|
|
1110
|
-
|
|
1111
|
-
# Write registry
|
|
1285
|
+
# Use helper for confirm/apply
|
|
1112
1286
|
output = {"implementations": implementations}
|
|
1113
|
-
|
|
1114
|
-
with open(registry_path, "w") as f:
|
|
1115
|
-
yaml.dump(output, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
1116
|
-
|
|
1117
|
-
print(f"\n✅ Coder registry updated successfully!")
|
|
1118
|
-
print(f" • Updated {stats['updated']} implementations")
|
|
1119
|
-
print(f" • Added {stats['new']} new implementations")
|
|
1120
|
-
print(f" 📝 Registry: {registry_path}")
|
|
1287
|
+
return self._confirm_and_apply(mode, "coder", registry_path, output, stats)
|
|
1121
1288
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
def build_supabase(self, preview_only: bool = False) -> Dict[str, Any]:
|
|
1289
|
+
def build_supabase(self, mode: str = "interactive", preview_only: bool = None) -> Dict[str, Any]:
|
|
1125
1290
|
"""
|
|
1126
1291
|
Build supabase functions registry.
|
|
1127
1292
|
Scans supabase/functions/**/ for function directories.
|
|
1293
|
+
|
|
1294
|
+
Args:
|
|
1295
|
+
mode: "interactive" (prompt), "apply" (no prompt), or "check" (verify only)
|
|
1296
|
+
preview_only: Deprecated - use mode="check" instead
|
|
1128
1297
|
"""
|
|
1298
|
+
# Backwards compatibility
|
|
1299
|
+
if preview_only is not None:
|
|
1300
|
+
mode = "check" if preview_only else "interactive"
|
|
1129
1301
|
print("\n📊 Analyzing supabase registry from function files...")
|
|
1130
1302
|
|
|
1131
1303
|
# Load existing registry
|
|
@@ -1134,7 +1306,7 @@ class RegistryBuilder:
|
|
|
1134
1306
|
if registry_path.exists():
|
|
1135
1307
|
with open(registry_path) as f:
|
|
1136
1308
|
registry_data = yaml.safe_load(f)
|
|
1137
|
-
existing_funcs = {
|
|
1309
|
+
existing_funcs = {fn.get("id"): fn for fn in registry_data.get("functions", [])}
|
|
1138
1310
|
|
|
1139
1311
|
functions = []
|
|
1140
1312
|
stats = {
|
|
@@ -1143,6 +1315,7 @@ class RegistryBuilder:
|
|
|
1143
1315
|
"updated": 0,
|
|
1144
1316
|
"new": 0,
|
|
1145
1317
|
"errors": 0,
|
|
1318
|
+
"preserved_drafts": 0,
|
|
1146
1319
|
"changes": []
|
|
1147
1320
|
}
|
|
1148
1321
|
|
|
@@ -1168,7 +1341,6 @@ class RegistryBuilder:
|
|
|
1168
1341
|
"description": f"Supabase function: {func_id}"
|
|
1169
1342
|
}
|
|
1170
1343
|
|
|
1171
|
-
# Track changes
|
|
1172
1344
|
if func_id in existing_funcs:
|
|
1173
1345
|
stats["updated"] += 1
|
|
1174
1346
|
else:
|
|
@@ -1186,37 +1358,24 @@ class RegistryBuilder:
|
|
|
1186
1358
|
print(f" ⚠️ Error processing {func_dir}: {e}")
|
|
1187
1359
|
stats["errors"] += 1
|
|
1188
1360
|
|
|
1361
|
+
# Preserve draft functions (path doesn't exist or draft: true)
|
|
1362
|
+
for func_id, func in existing_funcs.items():
|
|
1363
|
+
is_draft = func.get("draft", False)
|
|
1364
|
+
path_exists = func.get("path") and (self.repo_root / func.get("path")).exists()
|
|
1365
|
+
if is_draft or not path_exists:
|
|
1366
|
+
if func_id not in [fn.get("id") for fn in functions]:
|
|
1367
|
+
functions.append(func)
|
|
1368
|
+
stats["preserved_drafts"] += 1
|
|
1369
|
+
|
|
1189
1370
|
# Show preview
|
|
1190
1371
|
print(f"\n📋 PREVIEW:")
|
|
1191
1372
|
print(f" • {stats['updated']} functions will be updated")
|
|
1192
1373
|
print(f" • {stats['new']} new functions will be added")
|
|
1374
|
+
print(f" • {stats['preserved_drafts']} draft functions will be preserved")
|
|
1193
1375
|
|
|
1194
|
-
|
|
1195
|
-
print("\n⚠️ Preview mode - no changes applied")
|
|
1196
|
-
return stats
|
|
1197
|
-
|
|
1198
|
-
# Ask for confirmation
|
|
1199
|
-
print("\n❓ Do you want to apply these changes to the supabase registry?")
|
|
1200
|
-
print(" Type 'yes' to confirm, or anything else to cancel:")
|
|
1201
|
-
response = input(" > ").strip().lower()
|
|
1202
|
-
|
|
1203
|
-
if response != "yes":
|
|
1204
|
-
print("\n❌ Update cancelled by user")
|
|
1205
|
-
stats["cancelled"] = True
|
|
1206
|
-
return stats
|
|
1207
|
-
|
|
1208
|
-
# Write registry
|
|
1376
|
+
# Use helper for confirm/apply
|
|
1209
1377
|
output = {"functions": functions}
|
|
1210
|
-
|
|
1211
|
-
with open(registry_path, "w") as f:
|
|
1212
|
-
yaml.dump(output, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
1213
|
-
|
|
1214
|
-
print(f"\n✅ Supabase registry updated successfully!")
|
|
1215
|
-
print(f" • Updated {stats['updated']} functions")
|
|
1216
|
-
print(f" • Added {stats['new']} new functions")
|
|
1217
|
-
print(f" 📝 Registry: {registry_path}")
|
|
1218
|
-
|
|
1219
|
-
return stats
|
|
1378
|
+
return self._confirm_and_apply(mode, "supabase", registry_path, output, stats)
|
|
1220
1379
|
|
|
1221
1380
|
def build_python_manifest(self, preview_only: bool = False) -> Dict[str, Any]:
|
|
1222
1381
|
"""
|
|
@@ -1317,19 +1476,24 @@ class RegistryBuilder:
|
|
|
1317
1476
|
|
|
1318
1477
|
return stats
|
|
1319
1478
|
|
|
1320
|
-
def build_all(self) -> Dict[str, Any]:
|
|
1321
|
-
"""Build all registries.
|
|
1479
|
+
def build_all(self, mode: str = "interactive") -> Dict[str, Any]:
|
|
1480
|
+
"""Build all registries.
|
|
1481
|
+
|
|
1482
|
+
Args:
|
|
1483
|
+
mode: "interactive" (prompt), "apply" (no prompt), or "check" (verify only)
|
|
1484
|
+
"""
|
|
1322
1485
|
print("=" * 60)
|
|
1323
1486
|
print("Unified Registry Builder - Synchronizing from source files")
|
|
1324
1487
|
print("=" * 60)
|
|
1325
1488
|
|
|
1326
1489
|
results = {
|
|
1327
|
-
"plan": self.build_planner(),
|
|
1328
|
-
"
|
|
1329
|
-
"
|
|
1330
|
-
"
|
|
1331
|
-
"
|
|
1332
|
-
"
|
|
1490
|
+
"plan": self.build_planner(mode),
|
|
1491
|
+
"trains": self.build_trains(mode),
|
|
1492
|
+
"contracts": self.build_contracts(mode),
|
|
1493
|
+
"telemetry": self.build_telemetry(mode),
|
|
1494
|
+
"tester": self.build_tester(mode),
|
|
1495
|
+
"coder": self.build_coder(mode),
|
|
1496
|
+
"supabase": self.build_supabase(mode)
|
|
1333
1497
|
}
|
|
1334
1498
|
|
|
1335
1499
|
print("\n" + "=" * 60)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
atdd/__init__.py,sha256=-S8i9OahH-t9FJkPn6nprxipnjVum3rLeVsCS74T6eY,156
|
|
2
2
|
atdd/__main__.py,sha256=B0sXDQLjFN9GowTlXo4NMWwPZPjDsrT8Frq7DnbdOD8,77
|
|
3
|
-
atdd/cli.py,sha256=
|
|
3
|
+
atdd/cli.py,sha256=5O5IazSmlAPyIDmtOXE1Wi7RT_-FmtagkzETmPszE9E,22506
|
|
4
4
|
atdd/conftest.py,sha256=Fj3kIhCETbj2QBCIjySBgdS3stKNRZcZzKTJr7A4LaQ,5300
|
|
5
5
|
atdd/version_check.py,sha256=BlPcLwzNnm457vWUolB3wMtZEI-fXvpzfm6p8U_j7rc,6684
|
|
6
6
|
atdd/coach/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -14,7 +14,7 @@ atdd/coach/commands/initializer.py,sha256=wuvzj7QwA11ilNjRZU6Bx2bLQXITdBHJxR9_mZ
|
|
|
14
14
|
atdd/coach/commands/interface.py,sha256=FwBrJpWkfSL9n4n0HT_EC-alseXgU0bweKD4TImyHN0,40483
|
|
15
15
|
atdd/coach/commands/inventory.py,sha256=qU42MnkXt1JSBh5GU7pPSKmCO27Zfga7XwMT19RquJE,20969
|
|
16
16
|
atdd/coach/commands/migration.py,sha256=wRxU7emvvHqWt1MvXKkNTkPBjp0sU9g8F5Uy5yV2YfI,8177
|
|
17
|
-
atdd/coach/commands/registry.py,sha256=
|
|
17
|
+
atdd/coach/commands/registry.py,sha256=zE_Djgo4QKaDnp9JiPAh0D9wIv5Ed1yTmMEy31ipJkY,66736
|
|
18
18
|
atdd/coach/commands/session.py,sha256=MhuWXd5TR6bB3w0t8vANeZx3L476qwLT6EUQMwg-wQA,14268
|
|
19
19
|
atdd/coach/commands/sync.py,sha256=SLNzhcc6IuzMofMbkH9wM9rBSk5tPfcWPKXn9TaSZ-Y,13782
|
|
20
20
|
atdd/coach/commands/test_interface.py,sha256=a7ut2Hhk0PnQ5LfJZkoQwfkfkVuB5OHA4QBwOS0-jcg,16870
|
|
@@ -182,9 +182,9 @@ atdd/tester/validators/test_red_supabase_layer_structure.py,sha256=zbUjsMWSJE1MP
|
|
|
182
182
|
atdd/tester/validators/test_telemetry_structure.py,sha256=uU5frZnxSlOn60iHyqhe7Pg9b0wrOV7N14D4S6Aw6TE,22626
|
|
183
183
|
atdd/tester/validators/test_typescript_test_naming.py,sha256=E-TyGv_GVlTfsbyuxrtv9sOWSZS_QcpH6rrJFbWoeeU,11280
|
|
184
184
|
atdd/tester/validators/test_typescript_test_structure.py,sha256=eV89SD1RaKtchBZupqhnJmaruoROosf3LwB4Fwe4UJI,2612
|
|
185
|
-
atdd-0.4.
|
|
186
|
-
atdd-0.4.
|
|
187
|
-
atdd-0.4.
|
|
188
|
-
atdd-0.4.
|
|
189
|
-
atdd-0.4.
|
|
190
|
-
atdd-0.4.
|
|
185
|
+
atdd-0.4.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
186
|
+
atdd-0.4.6.dist-info/METADATA,sha256=krXgWDxwrC0W-HkHgBtc0GoE0aXyJWqhHGPpjm3G4T8,8716
|
|
187
|
+
atdd-0.4.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
188
|
+
atdd-0.4.6.dist-info/entry_points.txt,sha256=-C3yrA1WQQfN3iuGmSzPapA5cKVBEYU5Q1HUffSJTbY,38
|
|
189
|
+
atdd-0.4.6.dist-info/top_level.txt,sha256=VKkf6Uiyrm4RS6ULCGM-v8AzYN8K2yg8SMqwJLoO-xs,5
|
|
190
|
+
atdd-0.4.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|