import os,sys,json,time,pathlib,subprocess,urllib.request,datetime,signal
ROOT=pathlib.Path(os.environ['PROOF_SCRATCH'])
SIDE=sys.argv[1]
CHECKOUT=pathlib.Path(sys.argv[2])
RUN=ROOT/(SIDE+'-'+datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%S'))
RUN.mkdir(); HOME=RUN/'home'; HOME.mkdir(); (HOME/'userdata').mkdir()
(HOME/'userdata/settings.json').write_text(json.dumps({'providerInstances':{k:{'driver':k,'enabled':False} for k in ['claudeAgent','cursor','grok','muse','pi','opencode','antigravity']}}))
PORT=19541 if SIDE=='base' else 19542
ENV={**os.environ,'T3CODE_DEV_SEED':'0','T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD':'0'}
def cli(*args):
 return subprocess.check_output(['node','apps/server/src/bin.ts',*args,'--base-dir',str(HOME)],cwd=CHECKOUT,env=ENV,text=True)
def start(label):
 log=open(RUN/(label+'.log'),'w')
 p=subprocess.Popen(['node','apps/server/src/bin.ts','serve','--host','127.0.0.1','--port',str(PORT),'--base-dir',str(HOME)],cwd=CHECKOUT,env=ENV,stdout=log,stderr=subprocess.STDOUT)
 (RUN/'pid').write_text(str(p.pid))
 for _ in range(180):
  if p.poll() is not None: raise RuntimeError('Server exited: '+str(RUN/(label+'.log')))
  try:
   urllib.request.urlopen(f'http://127.0.0.1:{PORT}/health',timeout=1); return p
  except Exception:
   if (HOME/'userdata/server-runtime.json').exists(): return p
   time.sleep(1)
 raise RuntimeError('Server startup timed out')
def stop(p):
 p.send_signal(signal.SIGTERM)
 try:p.wait(timeout=15)
 except subprocess.TimeoutExpired:p.kill();p.wait()
class MCP:
 def __init__(self,token):
  self.headers={'Content-Type':'application/json','Accept':'application/json, text/event-stream','Authorization':'Bearer '+token};self.id=0
  self.send('initialize',{'protocolVersion':'2025-06-18','capabilities':{},'clientInfo':{'name':'batch-proof','version':'1'}})
  self.send('notifications/initialized',None,False)
 def send(self,method,params,reply=True):
  self.id+=1;payload={'jsonrpc':'2.0','method':method}
  if reply:payload['id']=self.id
  if params is not None:payload['params']=params
  req=urllib.request.Request(f'http://127.0.0.1:{PORT}/mcp',data=json.dumps(payload).encode(),headers=self.headers)
  with urllib.request.urlopen(req,timeout=120) as r:
   sid=r.headers.get('mcp-session-id')
   if sid:self.headers['mcp-session-id']=sid
   raw=r.read().decode()
  if not reply:return
  if raw.startswith('event:') or raw.startswith('data:'):
   vals=[json.loads(s[5:].strip()) for s in raw.splitlines() if s.startswith('data:')];ans=next(x for x in vals if x.get('id')==self.id)
  else:ans=json.loads(raw)
  if 'error' in ans:raise RuntimeError(ans)
  return ans['result']
 def call(self,name,args):
  ans=self.send('tools/call',{'name':name,'arguments':args})
  if ans.get('isError'):raise RuntimeError(ans)
  (RUN/(name+'.json')).write_text(json.dumps(ans,indent=2))
  return ans.get('structuredContent',ans)
p=None
try:
 print('RUN',RUN,flush=True);p=start('seed')
 token=subprocess.check_output(['node',str(ROOT/'mcp-token.mjs'),str(HOME)],cwd=CHECKOUT,env=ENV,text=True).strip()
 m=MCP(token)
 caps=m.call('orchestrator_capabilities',{})
 project=m.call('t3_project_create',{'title':'Batching proof','workspaceRoot':str(RUN/'project'),'createWorkspaceRootIfMissing':True})
 print('PROJECT created',flush=True)
 thread=m.call('t3_thread_launch',{'projectId':project['id'],'title':'110 linked pull requests','modelSelection':json.loads(os.environ['PROOF_MODEL_SELECTION'])})
 print('THREAD created idle',flush=True)
 threadId=thread['threadId']
 links=m.call('link_pull_requests',{'threadId':threadId,'urls':json.loads((ROOT/'pr-urls.json').read_text())})
 print('LINKED',links,flush=True)
 # Let the seed run finish its reads before stopping; evidence is only the following cold startup.
 time.sleep(30)
 stop(p);p=None
 cache=HOME/'caches/pull-requests'
 if cache.exists():cache.rename(HOME/'caches/pull-requests-seed')
 trace=HOME/'userdata/logs/server.trace.ndjson'
 if trace.exists():trace.rename(RUN/'seed.trace.ndjson')
 p=start('cold')
 for _ in range(180):
  if trace.exists():
   rows=[]
   for line in trace.read_text().splitlines():
    try:rows.append(json.loads(line))
    except:pass
   sweeps=[r for r in rows if r.get('name')=='PullRequestSyncReactor.sweep']
   if sweeps:
    (RUN/'cold.trace.ndjson').write_text(trace.read_text())
    print('SWEEP',json.dumps(sweeps[0]),flush=True);break
  time.sleep(1)
 else:raise RuntimeError('No sweep completion found')
 time.sleep(3)
 stop(p);p=None
 (RUN/'cold.trace.ndjson').write_text(trace.read_text())
 print('DONE',RUN,flush=True)
finally:
 if p:stop(p)
