249 lines
8.7 KiB
Python
Executable File
249 lines
8.7 KiB
Python
Executable File
#!/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())
|