#!/usr/bin/env python3 """ Signet Identity Functional Test Tests that all harnesses are correctly configured to share identity. Verifies config files, symlinks, and (optionally) live harness responses. Run: python ~/.agents/tests/test_identity.py [--live] """ import subprocess import sys import re import os import signal from pathlib import Path EXPECTED_NAME = "Mr. Claude" EXPECTED_OPERATOR = "Nicholai" LIVE_MODE = "--live" in sys.argv def test_agents_md_content(): """Test AGENTS.md contains correct identity.""" print("1. Testing AGENTS.md content...") agents_md = Path.home() / ".agents/AGENTS.md" if not agents_md.exists(): print(f" ✗ AGENTS.md not found: {agents_md}") return False content = agents_md.read_text() has_name = EXPECTED_NAME in content has_operator = EXPECTED_OPERATOR in content if has_name and has_operator: print(f" ✓ AGENTS.md contains '{EXPECTED_NAME}' and '{EXPECTED_OPERATOR}'") return True else: print(f" ✗ Identity not found in AGENTS.md") return False def test_claude_code_config(): """Test Claude Code config is generated from AGENTS.md.""" print("\n2. Testing Claude Code config...") agents_md = Path.home() / ".agents/AGENTS.md" claude_md = Path.home() / ".claude/CLAUDE.md" if not claude_md.exists(): print(f" ✗ Claude Code config missing: {claude_md}") return False agents_content = agents_md.read_text() claude_content = claude_md.read_text() # Check if AGENTS.md content is present in generated file # (generated files have a header, so we check for content inclusion) if agents_content in claude_content: print(f" ✓ Claude Code config contains AGENTS.md") return True elif EXPECTED_NAME in claude_content and EXPECTED_OPERATOR in claude_content: print(f" ~ Claude Code config has identity (different format)") return True else: print(f" ✗ Claude Code config missing identity") return False def test_opencode_config(): """Test OpenCode config is generated from AGENTS.md.""" print("\n3. Testing OpenCode config...") agents_md = Path.home() / ".agents/AGENTS.md" opencode_md = Path.home() / ".config/opencode/AGENTS.md" if not opencode_md.exists(): print(f" ✗ OpenCode config missing: {opencode_md}") return False agents_content = agents_md.read_text() opencode_content = opencode_md.read_text() # Check if AGENTS.md content is present in generated file if agents_content in opencode_content: print(f" ✓ OpenCode config contains AGENTS.md") return True elif EXPECTED_NAME in opencode_content and EXPECTED_OPERATOR in opencode_content: print(f" ~ OpenCode config has identity (different format)") return True else: print(f" ✗ OpenCode config missing identity") return False def test_openclaw_config(): """Test OpenClaw reads AGENTS.md from workspace.""" print("\n4. Testing OpenClaw config...") agents_md = Path.home() / ".agents/AGENTS.md" # OpenClaw uses workspace context, which includes AGENTS.md # Just verify the file exists and is readable if agents_md.exists() and agents_md.is_file(): content = agents_md.read_text() if EXPECTED_NAME in content and EXPECTED_OPERATOR in content: print(f" ✓ OpenClaw workspace has AGENTS.md") return True print(f" ✗ OpenClaw workspace missing AGENTS.md") return False def test_live_claude_code(): """Test Claude Code responds with correct identity (requires API).""" print("\n5. Testing Claude Code identity (live)...") if not LIVE_MODE: print(" ⊘ Skipped (use --live to enable)") return True try: proc = subprocess.Popen( ["claude", "--print", "--dangerously-skip-permissions", "-p", "What is your name and who is your operator? Answer in one sentence."], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, start_new_session=True ) try: stdout, stderr = proc.communicate(timeout=300) output = stdout + stderr except subprocess.TimeoutExpired: os.killpg(os.getpgid(proc.pid), signal.SIGKILL) proc.wait() print(" ✗ Claude Code timed out (5m)") return False output_lower = output.lower() has_name = "claude" in output_lower or "mr. claude" in output_lower has_operator = "nicholai" in output_lower if has_name and has_operator: print(f" ✓ Claude Code identifies correctly") return True else: print(f" ✗ Claude Code identity not confirmed") return False except FileNotFoundError: print(" ⊘ Claude Code not installed (skipped)") return True except Exception as e: print(f" ✗ Error: {e}") return False def test_live_opencode(): """Test OpenCode responds with correct identity (requires API).""" print("\n6. Testing OpenCode identity (live)...") if not LIVE_MODE: print(" ⊘ Skipped (use --live to enable)") return True try: proc = subprocess.Popen( ["opencode", "run", "What is your name and who is your operator? Answer in one sentence."], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, start_new_session=True ) try: stdout, stderr = proc.communicate(timeout=300) output = stdout + stderr except subprocess.TimeoutExpired: os.killpg(os.getpgid(proc.pid), signal.SIGKILL) proc.wait() print(" ✗ OpenCode timed out (5m)") return False # Strip ANSI codes ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') clean_output = ansi_escape.sub('', output) output_lower = clean_output.lower() has_name = "claude" in output_lower or "mr. claude" in output_lower has_operator = "nicholai" in output_lower if has_name and has_operator: print(f" ✓ OpenCode identifies correctly") return True else: print(f" ✗ OpenCode identity not confirmed") return False except FileNotFoundError: print(" ⊘ OpenCode not installed (skipped)") return True except Exception as e: print(f" ✗ Error: {e}") return False def main(): print("=" * 60) print("SIGNET IDENTITY TEST") print("=" * 60) if LIVE_MODE: print("Mode: LIVE (includes API calls, may take several minutes)") else: print("Mode: FAST (config/symlink verification only)") print("Use --live to include API response tests") print() tests = [ ("AGENTS.md content", test_agents_md_content), ("Claude Code config", test_claude_code_config), ("OpenCode config", test_opencode_config), ("OpenClaw config", test_openclaw_config), ] if LIVE_MODE: tests.extend([ ("Claude Code live", test_live_claude_code), ("OpenCode live", test_live_opencode), ]) results = [] for name, test_fn in tests: try: results.append(test_fn()) except Exception as e: print(f" ✗ Error: {e}") results.append(False) print() print("=" * 60) passed = sum(results) total = len(results) if all(results): print(f"RESULT: ALL TESTS PASSED ({passed}/{total})") print("=" * 60) return 0 else: print(f"RESULT: {passed}/{total} TESTS PASSED") print("=" * 60) return 1 if __name__ == "__main__": sys.exit(main())