#!/usr/bin/env python3 """ Fix Orquestra violations in new-mercadodevida. This script performs all the corrective actions identified in the audit. """ import json import shutil from datetime import datetime, timezone from pathlib import Path ROOT = Path(__file__).resolve().parents[1] BACKLOG_PATH = ROOT / 'backlog' / 'features.json' ARTIFACTS_DIR = ROOT / 'work' / 'artifacts' RUNTIME_STATUS = ROOT / 'work' / 'runtime-status.json' SPECS_DIR = ROOT / 'specs' LEGACY_DIR = ROOT / 'legacy' def now_iso(): return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace('+00:00', 'Z') def fix_specs_directory(): """Archive specs/ (plural) to legacy/""" if not SPECS_DIR.exists(): print(" āœ… specs/ directory does not exist (already clean)") return print(f" šŸ“¦ Moving specs/ to legacy/specs-old/") LEGACY_DIR.mkdir(exist_ok=True) dest = LEGACY_DIR / 'specs-old' if dest.exists(): shutil.rmtree(dest) shutil.move(str(SPECS_DIR), str(dest)) print(f" āœ… Archived specs/ to legacy/specs-old/") def fix_features_without_valid_gates(backlog): """Move features done without valid gates to pending""" fixed = [] for feature in backlog.get('features', []): if feature.get('status') != 'done': continue feature_id = feature.get('id') gates = feature.get('gates', {}) # Check if gates are valid (all required gates approved) required_gates = ['reviewer', 'security', 'qa'] has_valid_gates = all(gates.get(gate) is True for gate in required_gates) if not has_valid_gates: # Check if it's an old feature with 'review' instead of 'reviewer' if gates.get('review') is True and gates.get('security') is True and gates.get('qa') is True: # This is an old feature with correct gates but wrong nomenclature continue # Move to pending feature['status'] = 'pending' if 'completed_at' in feature: del feature['completed_at'] fixed.append(feature_id) return fixed def fix_features_without_artifacts(backlog): """Move features done without artifacts to pending""" fixed = [] for feature in backlog.get('features', []): if feature.get('status') != 'done': continue feature_id = feature.get('id') artifact_dir = ARTIFACTS_DIR / feature_id if not artifact_dir.exists(): # Feature is done but has no artifacts feature['status'] = 'pending' if 'completed_at' in feature: del feature['completed_at'] fixed.append(feature_id) return fixed def normalize_gate_nomenclature(backlog): """Change 'review' to 'reviewer' in features""" normalized = [] for feature in backlog.get('features', []): gates = feature.get('gates', {}) if 'review' in gates and 'reviewer' not in gates: gates['reviewer'] = gates.pop('review') feature['gates'] = gates normalized.append(feature.get('id')) return normalized def add_completed_at_to_done_features(backlog): """Add completed_at timestamp to features that are done but missing it""" fixed = [] for feature in backlog.get('features', []): if feature.get('status') == 'done' and not feature.get('completed_at'): # Use a default timestamp (we don't know when it was actually completed) feature['completed_at'] = '2026-08-17T00:00:00Z' fixed.append(feature.get('id')) return fixed def standardize_feature_schema(backlog): """Ensure all features have minimum required fields""" standardized = [] for feature in backlog.get('features', []): # Add missing fields with defaults if 'type' not in feature: feature['type'] = 'feature' if 'priority' not in feature: feature['priority'] = 'medium' if 'gates' not in feature: feature['gates'] = {} standardized.append(feature.get('id')) return standardized def sync_runtime_status(backlog): """Sync runtime-status.json with features.json""" if not RUNTIME_STATUS.exists(): print(" āš ļø runtime-status.json does not exist") return try: status = json.loads(RUNTIME_STATUS.read_text(encoding='utf-8')) feature_id = status.get('feature_id') if not feature_id: print(" āœ… No active feature in runtime-status.json") return # Find the feature in backlog feature_found = None for feature in backlog.get('features', []): if feature.get('id') == feature_id: feature_found = feature break if not feature_found: print(f" āš ļø Feature {feature_id} in runtime-status.json not found in backlog") return # Check if status matches runtime_state = status.get('state') backlog_status = feature_found.get('status') if backlog_status == 'pending' and runtime_state in ['running', 'done']: print(f" šŸ”§ Syncing runtime-status.json: {feature_id} is pending in backlog but {runtime_state} in runtime") status['state'] = 'waiting' status['stage'] = 'idle' status['action'] = 'Feature is pending in backlog' RUNTIME_STATUS.write_text(json.dumps(status, indent=2, ensure_ascii=False) + '\n', encoding='utf-8') print(f" āœ… Synced runtime-status.json for {feature_id}") else: print(f" āœ… runtime-status.json is consistent with backlog") except Exception as e: print(f" āŒ Error syncing runtime-status.json: {e}") def main(): print("šŸ”§ Fixing Orquestra violations in new-mercadodevida\n") # Load backlog if not BACKLOG_PATH.exists(): print(f"āŒ Backlog not found: {BACKLOG_PATH}") return 1 try: backlog = json.loads(BACKLOG_PATH.read_text(encoding='utf-8')) except Exception as e: print(f"āŒ Invalid backlog: {e}") return 1 # 1. Archive specs/ directory print("1ļøāƒ£ Archiving specs/ (plural) directory...") fix_specs_directory() print() # 2. Fix features without valid gates print("2ļøāƒ£ Fixing features without valid gates...") fixed_gates = fix_features_without_valid_gates(backlog) print(f" āœ… Moved {len(fixed_gates)} features to pending: {', '.join(fixed_gates[:10])}{'...' if len(fixed_gates) > 10 else ''}") print() # 3. Fix features without artifacts print("3ļøāƒ£ Fixing features without artifacts...") fixed_artifacts = fix_features_without_artifacts(backlog) print(f" āœ… Moved {len(fixed_artifacts)} features to pending: {', '.join(fixed_artifacts[:10])}{'...' if len(fixed_artifacts) > 10 else ''}") print() # 4. Normalize gate nomenclature print("4ļøāƒ£ Normalizing gate nomenclature (review → reviewer)...") normalized = normalize_gate_nomenclature(backlog) print(f" āœ… Normalized {len(normalized)} features: {', '.join(normalized[:10])}{'...' if len(normalized) > 10 else ''}") print() # 5. Add completed_at to done features print("5ļøāƒ£ Adding completed_at to done features...") fixed_timestamps = add_completed_at_to_done_features(backlog) print(f" āœ… Added timestamps to {len(fixed_timestamps)} features") print() # 6. Standardize feature schema print("6ļøāƒ£ Standardizing feature schema...") standardized = standardize_feature_schema(backlog) print(f" āœ… Standardized {len(standardized)} features") print() # 7. Sync runtime-status.json print("7ļøāƒ£ Syncing runtime-status.json with backlog...") sync_runtime_status(backlog) print() # Save backlog try: BACKLOG_PATH.write_text(json.dumps(backlog, indent=2, ensure_ascii=False) + '\n', encoding='utf-8') print("āœ… Saved updated backlog") except Exception as e: print(f"āŒ Cannot save backlog: {e}") return 1 # Summary print("\nšŸ“Š Summary:") print(f" - Features moved to pending (no valid gates): {len(fixed_gates)}") print(f" - Features moved to pending (no artifacts): {len(fixed_artifacts)}") print(f" - Features normalized (review → reviewer): {len(normalized)}") print(f" - Features with added timestamps: {len(fixed_timestamps)}") print(f" - Features standardized: {len(standardized)}") print("\nāœ… All fixes applied successfully") return 0 if __name__ == '__main__': import sys sys.exit(main())