Files
stack/agents/darkwing/work/slice1-s2-review/mutations-dw2.py
T
jason.woltjeandClaude Opus 5.5 1e3c06c78b docs(review): row 37 S2 round 2, approve (darkwing)
Round 2 of #1519: R1 to R5 fixed and reproduced, 43/43 on Node 26 and
Node 24, 22 of 23 mutants killed against a clean baseline. Records a
reparented-CLI path past the human proof for Sage's ruling.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:31:49 -05:00

33 lines
2.2 KiB
Python

#!/usr/bin/env python3
# Round 2: Rocko's cases plus Darkwing's boundary mutants, repository layout, each compared to a clean baseline.
import pathlib,tempfile,shutil,subprocess,json,re,sys
src=open(sys.argv[1]).read()
cases=eval(src[src.index('cases=[')+6:src.index('\n]\n')+2])
extra=[
('dw-eacces-all','human.mjs',"if(e.code!=='EACCES')throw e;",''),
('dw-command-skip-null-env','human.mjs',"const command=basename(current.argv[0]??'');","const command=current.env===null?'':basename(current.argv[0]??'');"),
('dw-launch-skip-null-env','human.mjs',"launches.some((r)=>r.pid===current.pid","current.env!==null&&launches.some((r)=>r.pid===current.pid"),
('dw-route-any-class','broker.mjs',"cls==='cross-role'&&proposedRoute===s.role","proposedRoute===s.role"),
('dw-reply-identifier','broker.mjs',"if(a[k]!==undefined){identifier(a[k]);","if(a[k]!==undefined){"),
('dw-token-15','credentials.mjs',"if(token.length<16)","if(token.length<15)"),
]
source=pathlib.Path(sys.argv[2])
def run(dest):
r=subprocess.run(['node','--test',*[str(p) for p in sorted((dest/'tests').glob('*.test.mjs'))]],capture_output=True,text=True,timeout=180)
tests=set(l[2:].rsplit(' (',1)[0] for l in r.stdout.splitlines() if l.startswith('✖ ') and not l.startswith('✖ failing tests'))
return r.returncode,tests
with tempfile.TemporaryDirectory(prefix='dw-bus-mut2-') as root:
dest=pathlib.Path(root)/'packages'/'bus';shutil.copytree(source,dest,ignore=shutil.ignore_patterns('dw'))
code,base=run(dest);print(json.dumps({'baseline_exit':code,'baseline_failures':sorted(base)}),flush=True)
code,f=run(dest);print(json.dumps({'mutation':'no-op','exit':code,'killed':bool(f-base)}),flush=True)
out=[]
for name,file,old,new in cases+extra:
p=dest/'src'/file;b=p.read_text();pat=r'\s*'.join(re.escape(c) for c in old if not c.isspace());n=len(re.findall(pat,b))
if n!=1: print(json.dumps({'mutation':name,'error':f'{n} matches'}),flush=True);continue
p.write_text(re.sub(pat,lambda m:new,b,count=1))
try: code,f=run(dest)
finally: p.write_text(b)
x=sorted(f-base);out.append({'mutation':name,'exit':code,'killed':bool(x),'new_failures':x})
print(json.dumps(out[-1]),flush=True)
print('killed',sum(x['killed'] for x in out),'of',len(out))