/ scripts / resonance_bootstrap.py
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()