/ scripts / sos_init.py
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
194195      └─ [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()