import os, subprocess, pathlib, time, socket, re, signal, sys
ROOT=pathlib.Path(__file__).resolve().parent
stamp=time.strftime('%Y%m%d-%H%M%S')
run=ROOT/stamp
run.mkdir()
def clean(s):
 s=re.sub(r'\x1b\[[0-9;]*m','',s)
 s=re.sub(r'https?://[^\s\x1b]*?/pair[^\s\x1b]*','[pairing URL redacted]',s)
 s=re.sub(r'([?&](?:token|code)=)[^\s&]+',r'\1[redacted]',s)
 s=s.replace(str(ROOT),'[scratch]').replace(str(pathlib.Path.home()),'[home]')
 s=re.sub(r'^Token:.*$|^App link:.*$', '[credential redacted]',s,flags=re.M)
 s='\n'.join(line for line in s.splitlines() if not any('\u2580'<=c<='\u259f' for c in line))
 return s
# Usage: python3 reproduce.py BASE_CHECKOUT HEAD_CHECKOUT
# Put tailscale-wrapper.py at ./bin/tailscale and chmod +x it first.
for side,checkout in [('before',sys.argv[1]),('after',sys.argv[2])]:
 cwd=pathlib.Path(checkout).resolve()/'apps/server'
 state=run/side
 env=dict(os.environ,PATH=str(ROOT/'bin')+':'+os.environ['PATH'],EVIDENCE_CALLS=str(run/(side+'-calls.txt')),T3CODE_DEV_SEED='0',NO_COLOR='1')
 args=['node','src/bin.ts','serve','--host','100.111.125.34','--port','39527','--tailscale-serve','--tailscale-serve-port','8527','--base-dir',str(state),'--no-browser']
 raw=run/(side+'-raw.txt')
 with raw.open('w') as f:
  proc=subprocess.Popen(args,cwd=cwd,env=env,stdout=f,stderr=subprocess.STDOUT,start_new_session=True)
  try:
   for _ in range(120):
    if proc.poll() is not None: break
    try:
     with socket.create_connection(('100.111.125.34',39527),timeout=.3): break
    except OSError: time.sleep(.5)
   time.sleep(3)
   pair=subprocess.run(['node','src/bin.ts','pair','--tailscale','--tailscale-serve-port','8527','--base-dir',str(state)],cwd=cwd,env=env,text=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,timeout=90)
  finally:
   os.killpg(proc.pid,signal.SIGTERM)
   try: proc.wait(timeout=15)
   except subprocess.TimeoutExpired: os.killpg(proc.pid,signal.SIGKILL);proc.wait()
 transcript='Environment: Linux CLI; real Tailscale 1.104.5. macOS target refusal simulated by PATH wrapper; all other calls use real CLI. Fresh isolated database.\n'
 transcript+='Command: node src/bin.ts serve --host 100.111.125.34 --port 39527 --tailscale-serve --tailscale-serve-port 8527 --base-dir [fresh-state] --no-browser\n\n'+raw.read_text()
 transcript+='\nCommand: node src/bin.ts pair --tailscale --tailscale-serve-port 8527 --base-dir [same-state]\n'+pair.stdout+'\nPair exit: '+str(pair.returncode)+'\n\nActual wrapped Serve calls:\n'+(run/(side+'-calls.txt')).read_text()
 (run/(side+'.txt')).write_text(clean(transcript))
 print(side,clean(transcript),flush=True)
print('RUN',run)
