|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +decision_engine.py — CONTINUITY LEGACY Pro |
| 4 | +========================================== |
| 5 | +Advanced Strategic Decision Engine (Alpha). |
| 6 | +
|
| 7 | +This module analyzes the canonical memory files (STATE.json, ROADMAP.md, LIVE_HANDOFF.md) |
| 8 | +to identify technical debt, project momentum stalls, and missing contexts. |
| 9 | +It outputs actionable strategic recommendations for the human / AI developer team. |
| 10 | +
|
| 11 | +Features: |
| 12 | +- State staleness detection |
| 13 | +- Roadmap alignment validation |
| 14 | +- Tactical extraction from LIVE_HANDOFF.md |
| 15 | +""" |
| 16 | + |
| 17 | +import json |
| 18 | +import datetime |
| 19 | +from pathlib import Path |
| 20 | + |
| 21 | +def parse_state(repo_root: Path) -> dict: |
| 22 | + """Safely loads and parses STATE.json.""" |
| 23 | + state_file = repo_root / "STATE.json" |
| 24 | + if not state_file.exists(): |
| 25 | + return {"status": "error", "message": "STATE.json not found."} |
| 26 | + try: |
| 27 | + with open(state_file, "r", encoding="utf-8") as f: |
| 28 | + return json.load(f) |
| 29 | + except Exception as e: |
| 30 | + return {"status": "error", "message": f"Malformed STATE.json: {e}"} |
| 31 | + |
| 32 | +def evaluate_momentum(state_data: dict) -> dict: |
| 33 | + """Evaluates project momentum based on the last update timestamp.""" |
| 34 | + try: |
| 35 | + last_update_str = state_data.get("last_update", state_data.get("generated_at", "")) |
| 36 | + if not last_update_str: |
| 37 | + return {"score": 50, "insight": "No timestamp found in STATE.json."} |
| 38 | + |
| 39 | + # Parse ISO format handling potential timezone strings (basic implementation) |
| 40 | + clean_str = last_update_str.replace("Z", "+00:00") |
| 41 | + last_update = datetime.datetime.fromisoformat(clean_str) |
| 42 | + |
| 43 | + # Calculate delta |
| 44 | + now = datetime.datetime.now(datetime.timezone.utc) |
| 45 | + delta = now - last_update |
| 46 | + |
| 47 | + if delta.days > 30: |
| 48 | + return {"score": 20, "insight": "Project stale. Over 30 days without updates."} |
| 49 | + elif delta.days > 7: |
| 50 | + return {"score": 60, "insight": "Momentum slowing. Over a week since last update."} |
| 51 | + else: |
| 52 | + return {"score": 95, "insight": "High momentum. Active development detected."} |
| 53 | + except Exception: |
| 54 | + return {"score": 50, "insight": "Could not parse last update time."} |
| 55 | + |
| 56 | +def extract_tactical_directive(repo_root: Path) -> str: |
| 57 | + """Extracts the immediate next action from LIVE_HANDOFF.md.""" |
| 58 | + handoff_file = repo_root / ".continuity" / "LIVE_HANDOFF.md" |
| 59 | + if not handoff_file.exists(): |
| 60 | + return "CRITICAL: LIVE_HANDOFF.md missing. Next exact action is undefined." |
| 61 | + |
| 62 | + try: |
| 63 | + content = handoff_file.read_text(encoding="utf-8") |
| 64 | + lines = content.splitlines() |
| 65 | + for idx, line in enumerate(lines): |
| 66 | + if "Next Exact Action" in line: |
| 67 | + # Capture the next non-empty line as the directive |
| 68 | + for next_line in lines[idx+1:idx+5]: |
| 69 | + stripped = next_line.strip() |
| 70 | + if stripped and not stripped.startswith("#"): |
| 71 | + return f"Directive found: {stripped}" |
| 72 | + return "Warning: Next Exact Action header found but no actionable step defined." |
| 73 | + except Exception: |
| 74 | + return "Error reading LIVE_HANDOFF.md." |
| 75 | + |
| 76 | +def generate_strategy(repo_root: Path) -> dict: |
| 77 | + """Orchestrates the decision engine analysis.""" |
| 78 | + print("[*] Engine Initializing: Strategic Analysis...") |
| 79 | + |
| 80 | + state = parse_state(repo_root) |
| 81 | + if state.get("status") == "error": |
| 82 | + print(f"[!] {state['message']}") |
| 83 | + return state |
| 84 | + |
| 85 | + momentum = evaluate_momentum(state) |
| 86 | + tactical = extract_tactical_directive(repo_root) |
| 87 | + |
| 88 | + # Formulate recommendation |
| 89 | + recommendation = "Continue execution of the roadmap." |
| 90 | + if momentum["score"] < 50: |
| 91 | + recommendation = "PRIORITY: Review STATE.json. Update current context to regain momentum." |
| 92 | + elif "missing" in tactical.lower() or "error" in tactical.lower(): |
| 93 | + recommendation = "PRIORITY: Re-establish the LIVE_HANDOFF.md to define the next exact action for the AI agent." |
| 94 | + |
| 95 | + report = { |
| 96 | + "status": "success", |
| 97 | + "generated_at": datetime.datetime.now(datetime.timezone.utc).isoformat(), |
| 98 | + "momentum_score": momentum["score"], |
| 99 | + "momentum_insight": momentum["insight"], |
| 100 | + "tactical_directive": tactical, |
| 101 | + "strategic_recommendation": recommendation |
| 102 | + } |
| 103 | + |
| 104 | + # Write output |
| 105 | + out_dir = repo_root / "outputs" / "continuity" |
| 106 | + out_dir.mkdir(parents=True, exist_ok=True) |
| 107 | + out_file = out_dir / "strategic_decision.json" |
| 108 | + |
| 109 | + with open(out_file, "w", encoding="utf-8") as f: |
| 110 | + json.dump(report, f, indent=2) |
| 111 | + |
| 112 | + print(f"[+] Advanced Strategy Report generated at: {out_file.relative_to(repo_root)}") |
| 113 | + print(f" -> Momentum: {momentum['score']}/100") |
| 114 | + print(f" -> Strategy: {recommendation}") |
| 115 | + |
| 116 | + return report |
| 117 | + |
| 118 | +if __name__ == "__main__": |
| 119 | + import argparse |
| 120 | + parser = argparse.ArgumentParser(description="CONTINUITY LEGACY Pro - Decision Engine") |
| 121 | + parser.add_argument("--repo-root", default=".", help="Root directory of the project") |
| 122 | + args = parser.parse_args() |
| 123 | + |
| 124 | + root_path = Path(args.repo_root).resolve() |
| 125 | + generate_strategy(root_path) |
0 commit comments