import io, subprocess, sys, os

SRC = r'C:\Users\decid\Documents\projects\spt-claude-code\tools\claude-spt\src\hook.rs'
MANIFEST = r'C:\Users\decid\Documents\projects\spt-claude-code\tools\claude-spt\Cargo.toml'
NL = chr(10)

MUTATIONS = [
    (
        'B1 quiet-window Stop stops reporting its batch (the dispatch drop)',
        '        // [impl->REQ-HAZARD-ACROSS-CLEAR-DISPATCH-DROP]' + NL + '        report_midturn_span(env, &id, &sid, &scan.authored);' + NL,
        '        // [impl->REQ-HAZARD-ACROSS-CLEAR-DISPATCH-DROP]' + NL,
        ['a_quiet_window_stop_still_publishes_its_batch_as_a_span'],
    ),
    (
        'B2 quiet-window Stop marks IDLE instead of staying busy',
        '        // [impl->REQ-HAZARD-ACROSS-CLEAR-DISPATCH-DROP]' + NL + '        report_midturn_span(env, &id, &sid, &scan.authored);',
        '        report_midturn_span(env, &id, &sid, &scan.authored);' + NL
        + '        env.spt(&["api", "--adapter", ADAPTER, "state", "idle", &id, "--session-id", &sid], None, &[]);',
        ['a_quiet_window_stop_still_publishes_its_batch_as_a_span'],
    ),
    (
        'B3 the span payload is trimmed before reporting (tag would not reach core)',
        '    let Some(span) = batch_payload(authored) else { return };',
        '    let Some(span) = batch_payload(authored).map(|s| s.chars().take(8).collect::<String>()) else { return };',
        ['a_shortform_tag_in_agent_output_makes_no_send_call'],
    ),
    (
        'B4 commune bodies stop routing to the commune file',
        '''    for body in &plan.communes {
        write_commune_file(env, id, body);''',
        '''    for body in &plan.communes {
        let _ = body;''',
        ['a_commune_still_routes_to_the_commune_file_after_the_parser_deletion',
         'scan_commune_shortcut_writes_file_and_escalates_checkpoint'],
    ),
]


def run():
    r = subprocess.run(['cargo', 'test', '--manifest-path', MANIFEST],
                       capture_output=True, text=True, errors='replace')
    return r.stdout + r.stderr


def failing(out):
    n = set()
    for line in out.splitlines():
        line = line.strip()
        if line.startswith('---- ') and line.endswith(' stdout ----'):
            n.add(line[5:-12].strip().split('::')[-1])
    return n


orig = io.open(SRC, encoding='utf-8').read()
ok = True
try:
    for name, anchor, repl, must_fail in MUTATIONS:
        c = orig.count(anchor)
        if c != 1:
            print('BAD ANCHOR %-58s count=%d' % (name, c))
            ok = False
            continue
        io.open(SRC, 'w', encoding='utf-8', newline=NL).write(orig.replace(anchor, repl, 1))
        out = run()
        if 'error[E' in out or 'could not compile' in out:
            print('DID NOT COMPILE %-53s' % name)
            for l in out.splitlines():
                if l.startswith('error'):
                    print('    ', l)
            ok = False
            continue
        f = failing(out)
        missed = [t for t in must_fail if t not in f]
        print('%s %-56s failed=%s' % ('CAUGHT ' if not missed else 'ESCAPED', name, sorted(f) or 'NONE'))
        if missed:
            print('         did NOT fail and should have:', missed)
            ok = False
finally:
    io.open(SRC, 'w', encoding='utf-8', newline=NL).write(orig)

print()
print('ALL MUTATIONS CAUGHT' if ok else 'SOME ESCAPED OR MISAPPLIED')
sys.exit(0 if ok else 1)
