# python3 -I 04_strings.py <repo> <strings.json>
# Independent extraction with ast (never executes the official code) and comparison with strings.gen.ts.
import ast, json, subprocess, sys, hashlib
repo, gen = sys.argv[1], json.load(open(sys.argv[2]))
C='624efc5'; P='93c1474'
def src(c,p): return subprocess.run(['git','-C',repo,'show',f'{c}:{p}'],capture_output=True,check=True).stdout.decode()
def tree(c,p): return ast.parse(src(c,p))
def fn(t,name):
    for n in ast.walk(t):
        if isinstance(n,(ast.FunctionDef,)) and n.name==name: return n
    raise KeyError(name)
def parts(n,s):
    if isinstance(n,ast.Constant): return n.value
    assert isinstance(n,ast.JoinedStr)
    out=[]
    for v in n.values:
        if isinstance(v,ast.Constant): out.append(v.value)
        else: out.append({'v':ast.unparse(v.value)})
    return out
def strval(n):
    if isinstance(n,ast.Constant): return n.value
    if isinstance(n,ast.BinOp) and isinstance(n.op,ast.Add): return strval(n.left)+strval(n.right)
    if isinstance(n,ast.BinOp) and isinstance(n.op,ast.Mult): return strval(n.left)*strval(n.right)
    raise ValueError(ast.dump(n))
res=[]
def cmp(name,a,b):
    ok=a==b; res.append((name,ok))
    print(('OK  ' if ok else 'DIFF'),name)
    if not ok: print('   official:',json.dumps(a)[:400]); print('   gen     :',json.dumps(b)[:400])
for name,c,p in [('utils',C,'gca_evaluation/utils.py'),('agent',C,'gca_evaluation/agent.py'),('main',C,'gca_evaluation/main.py'),('prompts',C,'gca_evaluation/prompts.py'),('utils_paper',P,'gca_evaluation/utils.py'),('eval_reproduce_paper',P,'gca_evaluation/eval_reproduce.py')]:
    h=hashlib.sha256(src(c,p).encode()).hexdigest(); cmp(f'SOURCES.{name}.sha256',h,gen['SOURCES'][name]['sha256'])
us=src(C,'gca_evaluation/utils.py'); ut=ast.parse(us)
gc=fn(ut,'get_character_prompt')
consts=[n for n in ast.walk(gc) if isinstance(n,ast.Assign) and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='output_format']
fmt=[n.value.value for n in consts]
cmp('outputFormat.speechAction',fmt[0],gen['CHARACTER_PROMPT']['outputFormat']['speechAction'])
cmp('outputFormat.thought',fmt[1],gen['CHARACTER_PROMPT']['outputFormat']['thought'])
cmp('outputFormat.thoughtWithExample',fmt[2],gen['CHARACTER_PROMPT']['outputFormat']['thoughtWithExample'])
dec=[n for n in ast.walk(gc) if isinstance(n,ast.Assign) and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='decorator'][0]
cmp('decorators',[strval(e) for e in dec.value.args[0].elts],gen['CHARACTER_PROMPT']['decorators'])
aug=[n for n in ast.walk(gc) if isinstance(n,ast.AugAssign) and isinstance(n.target,ast.Name) and n.target.id=='other_character_profiles_str'][0]
cmp('otherEntry',parts(aug.value,us),gen['CHARACTER_PROMPT']['otherEntry'])
ifs=[n for n in ast.walk(gc) if isinstance(n,ast.If) and isinstance(n.test,ast.Name) and n.test.id=='fixed_template'][0]
a=[n for n in ifs.body]
cmp('motivationSection',parts(a[0].body[0].value,us),gen['CHARACTER_PROMPT']['motivationSection'])
cmp('othersSection',parts(a[1].body[0].value,us),gen['CHARACTER_PROMPT']['othersSection'])
cmp('prompt',parts(a[2].value,us),gen['CHARACTER_PROMPT']['prompt'])
cmp('requirements',parts(a[4].value,us),gen['CHARACTER_PROMPT']['requirements'])
ge=fn(ut,'get_environment_prompt')
mr=[n for n in ge.body if isinstance(n,ast.Assign) and n.targets[0].id=='model_roles'][0]
cmp('modelRoles',[e.value for e in mr.value.elts],gen['ENVIRONMENT_PROMPT']['modelRoles'])
pr=[n for n in ge.body if isinstance(n,ast.Assign) and n.targets[0].id=='prompt'][0]
cmp('ENVIRONMENT_PROMPT.prompt',parts(pr.value,us),gen['ENVIRONMENT_PROMPT']['prompt'])
gn=fn(ut,'get_nsp_prompt'); pr=[n for n in gn.body if isinstance(n,ast.Assign) and n.targets[0].id=='prompt'][0]
cmp('NSP_PROMPT.prompt',parts(pr.value,us),gen['NSP_PROMPT']['prompt'])
ps=src(P,'gca_evaluation/utils.py'); pt=ast.parse(ps); gn2=fn(pt,'get_nsp_prompt'); pr2=[n for n in gn2.body if isinstance(n,ast.Assign) and n.targets[0].id=='prompt'][0]
cmp('PAPER_ERA_NSP_PROMPT.prompt',parts(pr2.value,ps),gen['PAPER_ERA_NSP_PROMPT']['prompt'])
en=fn(ut,'extract_nsp'); pat=[n for n in en.body if isinstance(n,ast.Assign)][0].value.value
cmp('NSP_PARSE.pattern',pat,gen['NSP_PARSE']['pattern'])
fj=fn(ut,'_fix_json'); pr=[n for n in fj.body if isinstance(n,ast.Assign) and n.targets[0].id=='prompt'][0]
cmp('JSON_FIX_PROMPT',parts(pr.value,us),gen['JSON_FIX_PROMPT'])
ats=src(C,'gca_evaluation/agent.py'); at=ast.parse(ats); init=[n for n in ast.walk(at) if isinstance(n,ast.FunctionDef) and n.name=='__init__' and any(isinstance(a,ast.arg) and a.arg=='retrieval_target' for a in n.args.args)][0]
aug=[n for n in ast.walk(init) if isinstance(n,ast.Assign) and isinstance(n.targets[0],ast.Attribute) and n.targets[0].attr=='system_prompt' and isinstance(n.value,ast.BinOp)]
cmp('AGENT.concise',aug[0].value.right.value,gen['AGENT']['concise'])
cmp('AGENT.namePrefix',parts(aug[1].value.right,ats),gen['AGENT']['namePrefix'])
pts=src(C,'gca_evaluation/prompts.py'); pa=ast.parse(pts); d=ast.literal_eval(pa.body[0].value)
cmp('JUDGE.template',d['self-play-deduct-template'],gen['JUDGE']['template'])
for k,v in d['dimension_details'].items():
    cmp(f'JUDGE.details.{k}.brief',v['dimension_brief'],gen['JUDGE']['details'][k]['dimension_brief'])
    cmp(f'JUDGE.details.{k}.criteria',v['dimension_criteria'],gen['JUDGE']['details'][k]['dimension_criteria'])
# 93c1474 critic strings vs 624efc5
pp=ast.parse(src(P,'gca_evaluation/prompts.py'))
cp=[n for n in pp.body if isinstance(n,ast.Assign) and n.targets[0].id=='critic_prompts'][0]
cmp('critic_prompts 93c1474 == 624efc5',ast.literal_eval(cp.value),d)
# replace chain order from the source
ms=src(C,'gca_evaluation/main.py'); line=[l for l in ms.split('\n') if "critic_prompts['self-play-deduct-template'].replace" in l][0]
import re
chain=re.findall(r"\.replace\('(\{[a-z_]+\})'",line)
cmp('JUDGE.replaceChain order',chain,[x['placeholder'] for x in gen['JUDGE']['replaceChain']])
# paper-era character prompt: same literals at 93c1474?
pg=fn(pt,'get_character_prompt'); pi=[n for n in ast.walk(pg) if isinstance(n,ast.If) and isinstance(n.test,ast.Name) and n.test.id=='fixed_template'][0]
b=pi.body
print('93c1474 fixed_template statements:',[ast.get_source_segment(ps,x)[:60] for x in b][:5])
print('FAIL count',sum(1 for _,o in res if not o),'of',len(res))
