Files
mercadodevida/scripts/fix_orquestra_violations.py
2026-08-17 22:23:10 +02:00

249 lines
8.7 KiB
Python
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())