/ scripts / sos_common.py
sos_common.py
  1  #!/usr/bin/env python3
  2  """
  3  Sovereign OS Common Utilities
  4  
  5  Shared utilities for all Sovereign OS scripts.
  6  Handles:
  7  - Repo root detection
  8  - Configuration loading
  9  - Path resolution
 10  
 11  This module makes all scripts portable across repos.
 12  """
 13  
 14  import json
 15  import os
 16  from pathlib import Path
 17  from typing import Optional, Dict, Any
 18  
 19  
 20  # =============================================================================
 21  # REPO DETECTION
 22  # =============================================================================
 23  
 24  def find_repo_root(start_path: Path = None) -> Path:
 25      """
 26      Find the repository root by looking for .sovereign-os.json or .git.
 27  
 28      Searches upward from start_path (or current script location).
 29      """
 30      if start_path is None:
 31          # Start from the script that called us
 32          start_path = Path(__file__).parent
 33  
 34      current = start_path.resolve()
 35  
 36      # First, look for .sovereign-os.json (installed Sovereign OS)
 37      while current != current.parent:
 38          if (current / '.sovereign-os.json').exists():
 39              return current
 40          current = current.parent
 41  
 42      # Fall back to looking for .git
 43      current = start_path.resolve()
 44      while current != current.parent:
 45          if (current / '.git').exists():
 46              return current
 47          current = current.parent
 48  
 49      # Last resort: return the start path
 50      return start_path.resolve()
 51  
 52  
 53  def is_sovereign_os_repo(repo_root: Path = None) -> bool:
 54      """Check if this repo has Sovereign OS installed."""
 55      if repo_root is None:
 56          repo_root = find_repo_root()
 57      return (repo_root / '.sovereign-os.json').exists()
 58  
 59  
 60  # =============================================================================
 61  # CONFIGURATION
 62  # =============================================================================
 63  
 64  DEFAULT_CONFIG = {
 65      "version": "1.0.0",
 66      "name": "Sovereign OS Instance",
 67      "paths": {
 68          "sessions": "sessions",
 69          "patterns": "patterns",
 70          "scripts": "scripts",
 71      },
 72      "thresholds": {
 73          "staleness_warning_minutes": 60,
 74          "staleness_critical_minutes": 180,
 75          "confidence_collapse": 0.4,
 76          "escalation_confidence": 0.4,
 77      }
 78  }
 79  
 80  
 81  def load_config(repo_root: Path = None) -> Dict[str, Any]:
 82      """
 83      Load Sovereign OS configuration.
 84  
 85      Returns default config if not installed.
 86      """
 87      if repo_root is None:
 88          repo_root = find_repo_root()
 89  
 90      config_file = repo_root / '.sovereign-os.json'
 91  
 92      if config_file.exists():
 93          try:
 94              return json.loads(config_file.read_text())
 95          except json.JSONDecodeError:
 96              pass
 97  
 98      return DEFAULT_CONFIG.copy()
 99  
100  
101  def save_config(config: Dict[str, Any], repo_root: Path = None):
102      """Save configuration to repo."""
103      if repo_root is None:
104          repo_root = find_repo_root()
105  
106      config_file = repo_root / '.sovereign-os.json'
107      config_file.write_text(json.dumps(config, indent=2))
108  
109  
110  # =============================================================================
111  # PATH RESOLUTION
112  # =============================================================================
113  
114  def get_sessions_dir(repo_root: Path = None) -> Path:
115      """Get the sessions directory path."""
116      if repo_root is None:
117          repo_root = find_repo_root()
118      config = load_config(repo_root)
119      return repo_root / config['paths']['sessions']
120  
121  
122  def get_live_compression_path(repo_root: Path = None) -> Path:
123      """Get the LIVE-COMPRESSION.md path."""
124      return get_sessions_dir(repo_root) / 'LIVE-COMPRESSION.md'
125  
126  
127  def get_insight_backlog_path(repo_root: Path = None) -> Path:
128      """Get the INSIGHT-BACKLOG.md path."""
129      return get_sessions_dir(repo_root) / 'INSIGHT-BACKLOG.md'
130  
131  
132  def get_daily_synthesis_path(repo_root: Path = None) -> Path:
133      """Get the DAILY-SYNTHESIS.md path."""
134      return get_sessions_dir(repo_root) / 'DAILY-SYNTHESIS.md'
135  
136  
137  def get_close_down_reports_dir(repo_root: Path = None) -> Path:
138      """Get the close-down-reports directory."""
139      return get_sessions_dir(repo_root) / 'close-down-reports'
140  
141  
142  def get_patterns_dir(repo_root: Path = None) -> Path:
143      """Get the patterns directory."""
144      if repo_root is None:
145          repo_root = find_repo_root()
146      config = load_config(repo_root)
147      return repo_root / config['paths']['patterns']
148  
149  
150  # =============================================================================
151  # THRESHOLD ACCESS
152  # =============================================================================
153  
154  def get_threshold(name: str, repo_root: Path = None) -> float:
155      """Get a threshold value from config."""
156      config = load_config(repo_root)
157      return config.get('thresholds', {}).get(name, DEFAULT_CONFIG['thresholds'].get(name, 0.5))
158  
159  
160  def get_staleness_warning_minutes(repo_root: Path = None) -> int:
161      """Get staleness warning threshold in minutes."""
162      return int(get_threshold('staleness_warning_minutes', repo_root))
163  
164  
165  def get_staleness_critical_minutes(repo_root: Path = None) -> int:
166      """Get staleness critical threshold in minutes."""
167      return int(get_threshold('staleness_critical_minutes', repo_root))
168  
169  
170  # =============================================================================
171  # UTILITIES
172  # =============================================================================
173  
174  def ensure_sessions_structure(repo_root: Path = None):
175      """Ensure the sessions directory structure exists."""
176      sessions_dir = get_sessions_dir(repo_root)
177      sessions_dir.mkdir(parents=True, exist_ok=True)
178      (sessions_dir / 'close-down-reports').mkdir(exist_ok=True)
179      (sessions_dir / 'synthesis').mkdir(exist_ok=True)
180  
181  
182  def get_repo_name(repo_root: Path = None) -> str:
183      """Get the name of this Sovereign OS instance."""
184      config = load_config(repo_root)
185      return config.get('name', 'Unknown')
186  
187  
188  # =============================================================================
189  # CLI (for testing)
190  # =============================================================================
191  
192  if __name__ == "__main__":
193      print("Sovereign OS Common Utilities")
194      print("=" * 40)
195  
196      repo_root = find_repo_root()
197      print(f"Repo root: {repo_root}")
198      print(f"Is Sovereign OS: {is_sovereign_os_repo(repo_root)}")
199  
200      config = load_config(repo_root)
201      print(f"Config: {json.dumps(config, indent=2)}")
202  
203      print(f"\nPaths:")
204      print(f"  Sessions: {get_sessions_dir(repo_root)}")
205      print(f"  Live Compression: {get_live_compression_path(repo_root)}")
206      print(f"  Insight Backlog: {get_insight_backlog_path(repo_root)}")
207      print(f"  Close Down Reports: {get_close_down_reports_dir(repo_root)}")