resonance_bootstrap.py
1 #!/usr/bin/env python3 2 """ 3 Resonance Bootstrap - Global context bootstrapping for Claude instances 4 5 This script runs at the START of any Claude Code session to inject 6 resonance-grounded context from the Sovereign OS system. 7 8 Usage: 9 python3 scripts/resonance_bootstrap.py # Get bootstrap context 10 python3 scripts/resonance_bootstrap.py --inject # Inject to CLAUDE.md 11 python3 scripts/resonance_bootstrap.py --install # Install hooks globally 12 python3 scripts/resonance_bootstrap.py --daemon # Run lifecycle daemon 13 14 Integration with Claude Code hooks: 15 Add to ~/.claude/settings.json: 16 { 17 "hooks": { 18 "PostToolUse": { 19 "command": "python3 /Users/rcerf/repos/Sovereign_OS/scripts/resonance_bootstrap.py --on-tool" 20 } 21 } 22 } 23 """ 24 25 import argparse 26 import json 27 import os 28 import sys 29 from datetime import datetime 30 from pathlib import Path 31 32 # Add Sovereign_OS to path 33 SOVEREIGN_OS_ROOT = Path(__file__).parent.parent 34 sys.path.insert(0, str(SOVEREIGN_OS_ROOT)) 35 36 from core.attention.resonance_lifecycle import ( 37 ResonanceLifecycleManager, 38 create_lifecycle_manager, 39 install_to_repo, 40 create_global_hook_config, 41 ) 42 43 # Also import resonance engine for axiom context 44 try: 45 from core.metacog.resonance import AXIOM_FIELDS, calculate_axiom_resonance 46 HAS_RESONANCE = True 47 except ImportError: 48 HAS_RESONANCE = False 49 AXIOM_FIELDS = {} 50 51 SESSIONS_DIR = SOVEREIGN_OS_ROOT / "sessions" 52 53 54 def get_bootstrap_context(include_axioms: bool = True) -> str: 55 """ 56 Generate bootstrap context for a new Claude session. 57 58 This is the main entry point for session bootstrapping. 59 Call this at the START of any session. 60 """ 61 manager = create_lifecycle_manager(str(SESSIONS_DIR), auto_start=False) 62 63 lines = [] 64 65 # Get resonance context 66 snapshot = manager.get_latest_snapshot() 67 if snapshot: 68 lines.append(snapshot.to_markdown()) 69 70 # Add axiom reference if requested 71 if include_axioms and HAS_RESONANCE: 72 lines.append("\n## Axiom Reference (A0-A4)") 73 for axiom_id, axiom in AXIOM_FIELDS.items(): 74 lines.append(f"- **{axiom_id}**: {axiom['name']}") 75 76 # Add session state from LIVE-COMPRESSION 77 live_file = SESSIONS_DIR / "LIVE-COMPRESSION.md" 78 if live_file.exists(): 79 lines.append("\n## Current Session State") 80 lines.append(f"See: {live_file}") 81 82 return "\n".join(lines) 83 84 85 def inject_to_current_session() -> bool: 86 """ 87 Inject bootstrap context into the current working directory's CLAUDE.md. 88 89 This allows any repo to benefit from resonance context. 90 """ 91 cwd = Path.cwd() 92 claude_md = cwd / "CLAUDE.md" 93 94 context = get_bootstrap_context() 95 96 # Create injection block 97 injection = f""" 98 <!-- BEGIN RESONANCE CONTEXT - Auto-injected {datetime.now().isoformat()} --> 99 <resonance-context> 100 {context} 101 </resonance-context> 102 <!-- END RESONANCE CONTEXT --> 103 """ 104 105 if claude_md.exists(): 106 existing = claude_md.read_text() 107 108 # Remove any existing injection 109 if "<!-- BEGIN RESONANCE CONTEXT" in existing: 110 start = existing.find("<!-- BEGIN RESONANCE CONTEXT") 111 end = existing.find("<!-- END RESONANCE CONTEXT -->") + len("<!-- END RESONANCE CONTEXT -->") 112 existing = existing[:start] + existing[end:] 113 114 # Append new injection 115 updated = existing.strip() + "\n\n" + injection 116 claude_md.write_text(updated) 117 else: 118 # Create minimal CLAUDE.md with injection 119 claude_md.write_text(f"# Claude Code Configuration\n{injection}") 120 121 print(f"Injected resonance context to {claude_md}") 122 return True 123 124 125 def install_globally() -> None: 126 """ 127 Install resonance hooks to common repo locations. 128 """ 129 repos_dir = Path.home() / "repos" 130 131 if not repos_dir.exists(): 132 print(f"Repos directory not found: {repos_dir}") 133 return 134 135 # Get list of repos 136 repos = [d for d in repos_dir.iterdir() if d.is_dir() and not d.name.startswith('.')] 137 138 print(f"Found {len(repos)} repos in {repos_dir}") 139 print() 140 141 installed = 0 142 for repo in repos: 143 if repo.name == "Sovereign_OS": 144 continue # Skip self 145 146 result = install_to_repo(str(repo)) 147 if result: 148 print(f" + {repo.name}") 149 installed += 1 150 else: 151 print(f" - {repo.name} (already installed)") 152 153 print() 154 print(f"Installed to {installed} repos") 155 156 157 def setup_claude_code_hooks() -> None: 158 """ 159 Set up Claude Code hooks for automatic resonance updates. 160 161 Creates/updates ~/.claude/settings.json with hooks. 162 """ 163 claude_dir = Path.home() / ".claude" 164 claude_dir.mkdir(exist_ok=True) 165 166 settings_file = claude_dir / "settings.json" 167 168 # Load existing settings 169 if settings_file.exists(): 170 settings = json.loads(settings_file.read_text()) 171 else: 172 settings = {} 173 174 # Add hooks 175 if "hooks" not in settings: 176 settings["hooks"] = {} 177 178 # Pre-tool hook for active updates 179 settings["hooks"]["PreToolUse"] = [ 180 { 181 "matcher": ".*", 182 "hooks": [ 183 { 184 "type": "command", 185 "command": f"python3 {SOVEREIGN_OS_ROOT}/scripts/resonance_bootstrap.py --on-tool-start 2>/dev/null || true" 186 } 187 ] 188 } 189 ] 190 191 # Session stop hook for pre-compaction flush 192 settings["hooks"]["Stop"] = [ 193 { 194 "hooks": [ 195 { 196 "type": "command", 197 "command": f"python3 {SOVEREIGN_OS_ROOT}/scripts/resonance_bootstrap.py --pre-compaction" 198 } 199 ] 200 } 201 ] 202 203 # Write settings 204 settings_file.write_text(json.dumps(settings, indent=2)) 205 print(f"Updated Claude Code hooks in {settings_file}") 206 207 208 def on_tool_start(tool_input: str = None) -> None: 209 """ 210 Called before tool use - records content for resonance. 211 """ 212 manager = create_lifecycle_manager(str(SESSIONS_DIR), auto_start=False) 213 214 if tool_input: 215 manager.record_content(tool_input) 216 217 # Check if update needed (> 60 seconds since last) 218 snapshot = manager.get_latest_snapshot() 219 if snapshot: 220 elapsed = (datetime.now() - snapshot.timestamp).total_seconds() 221 if elapsed < 60: 222 return # Too recent 223 224 # Create checkpoint 225 manager.update_now(source="active") 226 227 228 def pre_compaction_flush() -> None: 229 """ 230 Called before session compaction/end. 231 """ 232 manager = create_lifecycle_manager(str(SESSIONS_DIR), auto_start=False) 233 snapshot = manager.pre_compaction_flush() 234 print(f"Pre-compaction flush: checkpoint {snapshot.checkpoint}") 235 236 237 def post_compaction_bootstrap() -> None: 238 """ 239 Called after session compaction to restore context. 240 """ 241 manager = create_lifecycle_manager(str(SESSIONS_DIR), auto_start=False) 242 snapshot = manager.post_compaction_bootstrap() 243 if snapshot: 244 print(f"Post-compaction bootstrap: checkpoint {snapshot.checkpoint}") 245 else: 246 print("No previous state to restore") 247 248 249 def run_daemon() -> None: 250 """ 251 Run the resonance lifecycle manager as a background daemon. 252 """ 253 import time 254 255 print("Starting resonance lifecycle daemon...") 256 print(f"Sessions dir: {SESSIONS_DIR}") 257 print("Press Ctrl+C to stop") 258 print() 259 260 manager = create_lifecycle_manager(str(SESSIONS_DIR), auto_start=True) 261 262 try: 263 while True: 264 time.sleep(1) 265 except KeyboardInterrupt: 266 manager.stop_active_updates() 267 print("\nDaemon stopped") 268 269 270 def main(): 271 parser = argparse.ArgumentParser( 272 description="Resonance Bootstrap - Global context for Claude instances", 273 formatter_class=argparse.RawDescriptionHelpFormatter, 274 epilog=""" 275 Examples: 276 %(prog)s Get bootstrap context 277 %(prog)s --inject Inject context to current repo 278 %(prog)s --install Install hooks to all repos 279 %(prog)s --setup-hooks Set up Claude Code hooks 280 %(prog)s --daemon Run lifecycle daemon 281 """ 282 ) 283 284 parser.add_argument("--inject", action="store_true", 285 help="Inject context to current directory's CLAUDE.md") 286 parser.add_argument("--install", action="store_true", 287 help="Install hooks to all repos in ~/repos/") 288 parser.add_argument("--setup-hooks", action="store_true", 289 help="Set up Claude Code hooks in ~/.claude/") 290 parser.add_argument("--daemon", action="store_true", 291 help="Run as background daemon") 292 parser.add_argument("--pre-compaction", action="store_true", 293 help="Run pre-compaction flush") 294 parser.add_argument("--post-compaction", action="store_true", 295 help="Run post-compaction bootstrap") 296 parser.add_argument("--on-tool-start", action="store_true", 297 help="Called by hook on tool start") 298 parser.add_argument("--json", "-j", action="store_true", 299 help="Output in JSON format") 300 301 args = parser.parse_args() 302 303 if args.inject: 304 inject_to_current_session() 305 306 elif args.install: 307 install_globally() 308 309 elif args.setup_hooks: 310 setup_claude_code_hooks() 311 312 elif args.daemon: 313 run_daemon() 314 315 elif args.pre_compaction: 316 pre_compaction_flush() 317 318 elif args.post_compaction: 319 post_compaction_bootstrap() 320 321 elif args.on_tool_start: 322 # Read stdin for tool input 323 tool_input = sys.stdin.read() if not sys.stdin.isatty() else None 324 on_tool_start(tool_input) 325 326 else: 327 # Default: print bootstrap context 328 context = get_bootstrap_context() 329 330 if args.json: 331 manager = create_lifecycle_manager(str(SESSIONS_DIR), auto_start=False) 332 snapshot = manager.get_latest_snapshot() 333 if snapshot: 334 print(json.dumps(snapshot.to_dict(), indent=2, default=str)) 335 else: 336 print("{}") 337 else: 338 print(context) 339 340 341 if __name__ == "__main__": 342 main()