sos_init.py
1 #!/usr/bin/env python3 2 """ 3 Sovereign OS Protocol Installer 4 5 Initialize any repository with Sovereign OS protocols. 6 This creates the necessary structure and files for: 7 - Phoenix state tracking (LIVE-COMPRESSION.md) 8 - Hygiene enforcement 9 - Session spin up / close down 10 - Close down reports 11 12 Usage: 13 # Initialize current directory 14 python3 sos_init.py 15 16 # Initialize specific directory 17 python3 sos_init.py /path/to/repo 18 19 # Initialize with custom config 20 python3 sos_init.py --name "My Project" --axioms minimal 21 22 # Check if repo is initialized 23 python3 sos_init.py --check 24 25 # Upgrade existing installation 26 python3 sos_init.py --upgrade 27 """ 28 29 import argparse 30 import json 31 import os 32 import sys 33 from datetime import datetime 34 from pathlib import Path 35 from typing import Optional 36 37 # Version of the protocol 38 PROTOCOL_VERSION = "1.0.0" 39 40 41 # ============================================================================= 42 # CONFIGURATION 43 # ============================================================================= 44 45 DEFAULT_CONFIG = { 46 "version": PROTOCOL_VERSION, 47 "name": "Sovereign OS Instance", 48 "initialized_at": None, 49 "is_canonical": False, # Only Sovereign_OS itself is canonical 50 "canonical_os_path": None, # Path to Sovereign_OS for patterns/axioms 51 "axioms": "reference", # "reference" (point to OS), "embedded" (copy), "minimal" 52 "models": { 53 "primary": "opus", 54 "workers": ["sonnet"], 55 "fast": "haiku" 56 }, 57 "thresholds": { 58 "staleness_warning_minutes": 60, 59 "staleness_critical_minutes": 180, 60 "confidence_collapse": 0.4, 61 "escalation_confidence": 0.4, 62 }, 63 "paths": { 64 "sessions": "sessions", 65 "patterns": "patterns", # Only used if is_canonical 66 "scripts": "scripts", 67 } 68 } 69 70 71 # ============================================================================= 72 # TEMPLATES 73 # ============================================================================= 74 75 CLAUDE_MD_TEMPLATE = '''# {name} - Claude Protocol 76 77 *Sovereign OS Protocol v{version}* 78 79 --- 80 81 ## Canonical Reference 82 83 This repo runs Sovereign OS protocols. For canonical definitions: 84 85 **Sovereign OS Location:** `{canonical_os_path}` 86 87 | Resource | Location | 88 |----------|----------| 89 | Axioms (A0-A4) | `{canonical_os_path}/docs/principles/` | 90 | Patterns | `{canonical_os_path}/patterns/` | 91 | Core Protocol | `{canonical_os_path}/CLAUDE.md` | 92 93 --- 94 95 ## Session Startup 96 97 **On EVERY session start, run:** 98 99 ```bash 100 python3 {scripts_path}/spin_up.py 101 ``` 102 103 If issues detected, address BEFORE proceeding. 104 105 --- 106 107 ## Quick Reference 108 109 ### The Four Axioms 110 111 - **A0: Boundary Operation** - Every coherent system is Markov blankets within Markov blankets 112 - **A1: Telos of Integration** - Move toward connection, not isolation 113 - **A2: Recognition of Life** - Favor primitive over calcified; motion is life 114 - **A3: Dynamic Pole Navigation** - Navigate between poles; don't fix 115 116 For full axiom documentation: `{canonical_os_path}/docs/principles/bedrock-axioms.md` 117 118 ### Protocol Reminders 119 120 - Update `{sessions_path}/LIVE-COMPRESSION.md` at every significant checkpoint 121 - Run `{scripts_path}/close_down.py` at session end for full accounting 122 - Escalate when confidence < {confidence_threshold} or scope is wide 123 - Torah (compressed reports) + Talmud (LIVE-COMPRESSION.md) 124 125 ### Key Files (Local) 126 127 | File | Purpose | 128 |------|---------| 129 | `{sessions_path}/LIVE-COMPRESSION.md` | Phoenix state (high-fidelity session tracking) | 130 | `{sessions_path}/INSIGHT-BACKLOG.md` | Items to surface to user | 131 | `{sessions_path}/close-down-reports/` | Session accounting | 132 133 --- 134 135 ## Hygiene Enforcement 136 137 Hygiene checks run automatically via `spin_up.py`. To run manually: 138 139 ```bash 140 python3 {scripts_path}/phoenix_hygiene.py 141 ``` 142 143 Exit codes: 144 - 0 = All checks pass 145 - 1 = Warnings (address during session) 146 - 2 = Critical (fix before proceeding) 147 148 --- 149 150 ## Session Lifecycle 151 152 ``` 153 START END 154 │ │ 155 ▼ ▼ 156 spin_up.py ──────────────▶ close_down.py 157 │ │ 158 │ Update LIVE-COMPRESSION.md │ 159 │ at each checkpoint │ 160 └──────────────────────────────┘ 161 ``` 162 163 --- 164 165 *Sovereign OS Protocol v{version} | Installed {date} | References {canonical_os_path}* 166 ''' 167 168 LIVE_COMPRESSION_TEMPLATE = '''# Live Compression - {date}/new-session 169 170 *Maintained by [[First Officer]] - Session Tracking* 171 172 --- 173 174 - **metadata** 175 - updated:: {timestamp} 176 - confidence:: 0.95 177 - free_energy:: F = 0.10 178 - status:: NEW SESSION 179 - checkpoint:: 0 180 - mode:: Initializing 181 182 --- 183 184 ## Session Focus 185 186 *Describe the primary focus of this session* 187 188 --- 189 190 ## Thread Map 191 192 ``` 193 SESSION START 194 │ 195 └─ [NOW] Awaiting first task 196 ``` 197 198 --- 199 200 ## Gravity Wells (Active) 201 202 - **wells** 203 - (none yet) 204 205 --- 206 207 ## Key Insights (This Session) 208 209 (none yet) 210 211 --- 212 213 ## Artifacts Created 214 215 | Artifact | Type | Path | 216 |----------|------|------| 217 | (none yet) | | | 218 219 --- 220 221 ## Decision Points 222 223 ### Resolved 224 (none yet) 225 226 ### Pending 227 (none yet) 228 229 --- 230 231 ## Trust Log 232 233 - **errors** 234 - (none) 235 - **trust_f**:: 0.05 236 237 --- 238 239 ## Resurrection Seed 240 241 ```yaml 242 context: 243 session: new-session 244 date: {date} 245 primary_work: (not yet defined) 246 247 completed: [] 248 249 key_decisions: [] 250 251 bootstrap: 252 - Read: CLAUDE.md 253 254 f: 0.10 255 trust_f: 0.05 256 ``` 257 258 --- 259 260 *[[First Officer]] ACTIVE | Checkpoint #0 | New Session* 261 ''' 262 263 INSIGHT_BACKLOG_TEMPLATE = '''# Insight Backlog 264 265 *Items to surface to the user* 266 267 --- 268 269 ## High Priority 🔴 270 271 (none) 272 273 --- 274 275 ## Medium Priority 🟡 276 277 (none) 278 279 --- 280 281 ## Low Priority 🟢 282 283 (none) 284 285 --- 286 287 ## Completed ✓ 288 289 (none yet) 290 291 --- 292 293 *Updated: {timestamp}* 294 ''' 295 296 GITIGNORE_ADDITIONS = ''' 297 # Sovereign OS 298 sessions/close-down-reports/*.json 299 *.pyc 300 __pycache__/ 301 ''' 302 303 304 # ============================================================================= 305 # INITIALIZATION FUNCTIONS 306 # ============================================================================= 307 308 def find_repo_root(start_path: Path) -> Optional[Path]: 309 """Find the repository root by looking for .git directory.""" 310 current = start_path.resolve() 311 while current != current.parent: 312 if (current / '.git').exists(): 313 return current 314 current = current.parent 315 return start_path.resolve() # Fall back to start path 316 317 318 def is_initialized(repo_root: Path) -> bool: 319 """Check if repo already has Sovereign OS installed.""" 320 config_file = repo_root / ".sovereign-os.json" 321 return config_file.exists() 322 323 324 def load_config(repo_root: Path) -> dict: 325 """Load existing config or return defaults.""" 326 config_file = repo_root / ".sovereign-os.json" 327 if config_file.exists(): 328 return json.loads(config_file.read_text()) 329 return DEFAULT_CONFIG.copy() 330 331 332 def save_config(repo_root: Path, config: dict): 333 """Save configuration.""" 334 config_file = repo_root / ".sovereign-os.json" 335 config_file.write_text(json.dumps(config, indent=2)) 336 337 338 def create_directory_structure(repo_root: Path, config: dict): 339 """Create the directory structure.""" 340 sessions_path = repo_root / config['paths']['sessions'] 341 342 directories = [ 343 sessions_path, 344 sessions_path / "close-down-reports", 345 sessions_path / "synthesis", 346 sessions_path / "backfill", 347 repo_root / config['paths']['patterns'], 348 ] 349 350 for directory in directories: 351 directory.mkdir(parents=True, exist_ok=True) 352 print(f" ✓ Created {directory.relative_to(repo_root)}/") 353 354 355 def create_claude_md(repo_root: Path, config: dict): 356 """Create or update CLAUDE.md.""" 357 claude_md = repo_root / "CLAUDE.md" 358 359 content = CLAUDE_MD_TEMPLATE.format( 360 name=config['name'], 361 version=config['version'], 362 scripts_path=config['paths']['scripts'], 363 sessions_path=config['paths']['sessions'], 364 confidence_threshold=config['thresholds']['escalation_confidence'], 365 canonical_os_path=config.get('canonical_os_path', '(not configured)'), 366 date=datetime.now().strftime("%Y-%m-%d"), 367 ) 368 369 if claude_md.exists(): 370 # Backup existing 371 backup = repo_root / "CLAUDE.md.backup" 372 backup.write_text(claude_md.read_text()) 373 print(f" ⚠ Backed up existing CLAUDE.md to CLAUDE.md.backup") 374 375 claude_md.write_text(content) 376 print(f" ✓ Created CLAUDE.md") 377 378 379 def create_phoenix_files(repo_root: Path, config: dict): 380 """Create phoenix state files.""" 381 sessions_path = repo_root / config['paths']['sessions'] 382 now = datetime.now() 383 384 # LIVE-COMPRESSION.md 385 live_compression = sessions_path / "LIVE-COMPRESSION.md" 386 if not live_compression.exists(): 387 content = LIVE_COMPRESSION_TEMPLATE.format( 388 date=now.strftime("%Y-%m-%d"), 389 timestamp=now.isoformat(), 390 ) 391 live_compression.write_text(content) 392 print(f" ✓ Created {sessions_path.name}/LIVE-COMPRESSION.md") 393 else: 394 print(f" ○ {sessions_path.name}/LIVE-COMPRESSION.md already exists") 395 396 # INSIGHT-BACKLOG.md 397 insight_backlog = sessions_path / "INSIGHT-BACKLOG.md" 398 if not insight_backlog.exists(): 399 content = INSIGHT_BACKLOG_TEMPLATE.format( 400 timestamp=now.isoformat(), 401 ) 402 insight_backlog.write_text(content) 403 print(f" ✓ Created {sessions_path.name}/INSIGHT-BACKLOG.md") 404 else: 405 print(f" ○ {sessions_path.name}/INSIGHT-BACKLOG.md already exists") 406 407 408 def copy_scripts(repo_root: Path, config: dict): 409 """Copy protocol scripts to the repo.""" 410 scripts_path = repo_root / config['paths']['scripts'] 411 scripts_path.mkdir(parents=True, exist_ok=True) 412 413 # Get the source scripts directory (where this script lives) 414 source_scripts = Path(__file__).parent 415 416 scripts_to_copy = [ 417 'spin_up.py', 418 'phoenix_hygiene.py', 419 'close_down.py', 420 ] 421 422 for script_name in scripts_to_copy: 423 source = source_scripts / script_name 424 dest = scripts_path / script_name 425 426 if source.exists(): 427 # Read and adapt the script 428 content = source.read_text() 429 430 # Make paths configurable (they'll read from .sovereign-os.json) 431 dest.write_text(content) 432 print(f" ✓ Copied {script_name}") 433 else: 434 print(f" ⚠ Source script {script_name} not found") 435 436 437 def update_gitignore(repo_root: Path): 438 """Add Sovereign OS entries to .gitignore.""" 439 gitignore = repo_root / ".gitignore" 440 441 if gitignore.exists(): 442 content = gitignore.read_text() 443 if "# Sovereign OS" not in content: 444 content += "\n" + GITIGNORE_ADDITIONS 445 gitignore.write_text(content) 446 print(f" ✓ Updated .gitignore") 447 else: 448 print(f" ○ .gitignore already has Sovereign OS entries") 449 else: 450 gitignore.write_text(GITIGNORE_ADDITIONS) 451 print(f" ✓ Created .gitignore") 452 453 454 def initialize_repo( 455 repo_path: Path, 456 name: str = None, 457 axioms: str = "reference", 458 canonical_os_path: str = None, 459 force: bool = False 460 ) -> bool: 461 """ 462 Initialize a repository with Sovereign OS protocol. 463 464 Args: 465 repo_path: Path to the repo to initialize 466 name: Name for this instance 467 axioms: "reference" (point to OS), "embedded" (copy patterns), "minimal" 468 canonical_os_path: Path to Sovereign_OS (the canonical source) 469 force: Force reinstall 470 471 Returns True if successful. 472 """ 473 repo_root = find_repo_root(repo_path) 474 475 # Auto-detect canonical OS path if not provided 476 if canonical_os_path is None: 477 # Check common locations 478 possible_paths = [ 479 Path.home() / "repos" / "Sovereign_OS", 480 Path.home() / "Sovereign_OS", 481 repo_root.parent / "Sovereign_OS", 482 Path("/Users/rcerf/repos/Sovereign_OS"), # Fallback 483 ] 484 for p in possible_paths: 485 if (p / "CLAUDE.md").exists() and (p / "patterns").exists(): 486 canonical_os_path = str(p) 487 break 488 489 if canonical_os_path is None: 490 print("⚠ Could not auto-detect Sovereign_OS location.") 491 print(" Please specify with --canonical-os /path/to/Sovereign_OS") 492 canonical_os_path = "(not set - please configure)" 493 494 print(f"\n{'=' * 60}") 495 print(f"SOVEREIGN OS PROTOCOL INSTALLER v{PROTOCOL_VERSION}") 496 print(f"{'=' * 60}") 497 print(f"\nTarget: {repo_root}") 498 print() 499 500 # Check if already initialized 501 if is_initialized(repo_root) and not force: 502 print("⚠ Repository already initialized with Sovereign OS.") 503 print(" Use --upgrade to update, or --force to reinitialize.") 504 return False 505 506 # Create config 507 config = DEFAULT_CONFIG.copy() 508 config['name'] = name or repo_root.name 509 config['initialized_at'] = datetime.now().isoformat() 510 config['axioms'] = axioms 511 config['canonical_os_path'] = canonical_os_path 512 config['is_canonical'] = (str(repo_root) == canonical_os_path) 513 514 print("Installing Sovereign OS Protocol...\n") 515 516 # Create structure 517 print("Creating directory structure:") 518 create_directory_structure(repo_root, config) 519 print() 520 521 # Create files 522 print("Creating protocol files:") 523 create_claude_md(repo_root, config) 524 create_phoenix_files(repo_root, config) 525 print() 526 527 # Copy scripts 528 print("Installing scripts:") 529 copy_scripts(repo_root, config) 530 print() 531 532 # Update gitignore 533 print("Updating git configuration:") 534 update_gitignore(repo_root) 535 print() 536 537 # Save config 538 save_config(repo_root, config) 539 print(f"✓ Saved configuration to .sovereign-os.json") 540 541 print(f"\n{'=' * 60}") 542 print("INSTALLATION COMPLETE") 543 print(f"{'=' * 60}") 544 print(f""" 545 Next steps: 546 547 1. Start a new Claude Code session in this repo 548 549 2. The session will see CLAUDE.md and follow the protocol 550 551 3. To manually spin up: 552 python3 {config['paths']['scripts']}/spin_up.py 553 554 4. At session end: 555 python3 {config['paths']['scripts']}/close_down.py 556 557 """) 558 559 return True 560 561 562 def check_installation(repo_path: Path) -> dict: 563 """Check the status of Sovereign OS installation.""" 564 repo_root = find_repo_root(repo_path) 565 566 status = { 567 "initialized": is_initialized(repo_root), 568 "repo_root": str(repo_root), 569 "files": {}, 570 "issues": [], 571 } 572 573 if status["initialized"]: 574 config = load_config(repo_root) 575 status["config"] = config 576 577 # Check required files 578 required_files = [ 579 ("CLAUDE.md", repo_root / "CLAUDE.md"), 580 ("LIVE-COMPRESSION.md", repo_root / config['paths']['sessions'] / "LIVE-COMPRESSION.md"), 581 ("INSIGHT-BACKLOG.md", repo_root / config['paths']['sessions'] / "INSIGHT-BACKLOG.md"), 582 ("spin_up.py", repo_root / config['paths']['scripts'] / "spin_up.py"), 583 ("phoenix_hygiene.py", repo_root / config['paths']['scripts'] / "phoenix_hygiene.py"), 584 ] 585 586 for name, path in required_files: 587 exists = path.exists() 588 status["files"][name] = exists 589 if not exists: 590 status["issues"].append(f"Missing: {name}") 591 592 return status 593 594 595 def upgrade_installation(repo_path: Path) -> bool: 596 """Upgrade an existing Sovereign OS installation.""" 597 repo_root = find_repo_root(repo_path) 598 599 if not is_initialized(repo_root): 600 print("Repository not initialized. Run without --upgrade first.") 601 return False 602 603 config = load_config(repo_root) 604 old_version = config.get('version', 'unknown') 605 606 print(f"\nUpgrading from v{old_version} to v{PROTOCOL_VERSION}...") 607 608 # Update config version 609 config['version'] = PROTOCOL_VERSION 610 config['upgraded_at'] = datetime.now().isoformat() 611 612 # Re-copy scripts (they may have updates) 613 print("\nUpdating scripts:") 614 copy_scripts(repo_root, config) 615 616 # Save updated config 617 save_config(repo_root, config) 618 619 print(f"\n✓ Upgraded to v{PROTOCOL_VERSION}") 620 return True 621 622 623 # ============================================================================= 624 # CLI 625 # ============================================================================= 626 627 def main(): 628 parser = argparse.ArgumentParser( 629 description="Sovereign OS Protocol Installer", 630 formatter_class=argparse.RawDescriptionHelpFormatter, 631 epilog=""" 632 Examples: 633 %(prog)s Initialize current directory 634 %(prog)s /path/to/repo Initialize specific repo 635 %(prog)s --name "My Project" Initialize with custom name 636 %(prog)s --check Check installation status 637 %(prog)s --upgrade Upgrade existing installation 638 """ 639 ) 640 641 parser.add_argument('path', nargs='?', default='.', 642 help='Path to repository (default: current directory)') 643 parser.add_argument('--name', '-n', type=str, 644 help='Project name') 645 parser.add_argument('--axioms', choices=['reference', 'embedded', 'minimal'], 646 default='reference', help='How to handle axioms/patterns') 647 parser.add_argument('--canonical-os', type=str, 648 help='Path to Sovereign_OS (canonical source)') 649 parser.add_argument('--check', '-c', action='store_true', 650 help='Check installation status') 651 parser.add_argument('--upgrade', '-u', action='store_true', 652 help='Upgrade existing installation') 653 parser.add_argument('--force', '-f', action='store_true', 654 help='Force reinstallation') 655 parser.add_argument('--json', '-j', action='store_true', 656 help='Output in JSON format') 657 658 args = parser.parse_args() 659 660 repo_path = Path(args.path).resolve() 661 662 if args.check: 663 status = check_installation(repo_path) 664 if args.json: 665 print(json.dumps(status, indent=2)) 666 else: 667 print(f"\nSovereign OS Installation Status") 668 print(f"{'=' * 40}") 669 print(f"Repo: {status['repo_root']}") 670 print(f"Initialized: {'Yes' if status['initialized'] else 'No'}") 671 if status['initialized']: 672 print(f"Version: {status['config'].get('version', 'unknown')}") 673 print(f"\nFiles:") 674 for name, exists in status['files'].items(): 675 icon = "✓" if exists else "✗" 676 print(f" {icon} {name}") 677 if status['issues']: 678 print(f"\nIssues:") 679 for issue in status['issues']: 680 print(f" ⚠ {issue}") 681 sys.exit(0 if status['initialized'] and not status['issues'] else 1) 682 683 elif args.upgrade: 684 success = upgrade_installation(repo_path) 685 sys.exit(0 if success else 1) 686 687 else: 688 success = initialize_repo( 689 repo_path, 690 name=args.name, 691 axioms=args.axioms, 692 canonical_os_path=getattr(args, 'canonical_os', None), 693 force=args.force 694 ) 695 sys.exit(0 if success else 1) 696 697 698 if __name__ == "__main__": 699 main()