File size: 5,799 Bytes
ae4930d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python3
"""Session / progress tracker — adapted port of auto-re-agent's core/session.py,
reports/tracker.py, and reports/formatter.py. JSON-backed, append-only, atomic
writes. Mirrors the original's REPORT format so runs are resumable and progress
claims are auditable against recorded tool evidence (never the model's memory).

A "function" record = one analysis target (a gate/feature/function) the loop has
attempted, keyed by normalized address. Record after EACH attempt so a crash or
new session resumes where it left off.

Usage:
  python progress.py record --file re-progress.json --address 0x401abc \
     --name "CLicense::Validate" --component license \
     --success true --rounds 2 --verdict PASS --parity GREEN \
     --objective PASS --note "jz at 0x401ad0 gates the dialog"
  python progress.py summary --file re-progress.json
  python progress.py table   --file re-progress.json [--component license]
  python progress.py attempted --file re-progress.json --address 0x401abc   # exit 0 if attempted
"""
from __future__ import annotations

import argparse
import json
import sys
import time
from pathlib import Path


def norm_addr(a: str) -> str:
    a = a.strip().lower()
    if a.startswith("0x"):
        try:
            return hex(int(a, 16))
        except ValueError:
            return a
    return a


class Session:
    def __init__(self, path: str | Path):
        self.path = Path(path)
        self.data = {"functions": {}, "runs": []}
        if self.path.exists():
            try:
                self.data = json.loads(self.path.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError):
                pass
        self.data.setdefault("functions", {})
        self.data.setdefault("runs", [])

    def save(self):
        self.path.parent.mkdir(parents=True, exist_ok=True)
        tmp = self.path.with_suffix(".tmp")
        tmp.write_text(json.dumps(self.data, indent=2), encoding="utf-8")
        tmp.replace(self.path)

    def record(self, entry: dict):
        addr = norm_addr(entry["address"])
        entry["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%S")
        self.data["functions"][addr] = entry
        self.data["runs"].append(entry)
        self.save()

    def is_attempted(self, address: str) -> bool:
        return norm_addr(address) in self.data["functions"]

    def summary(self) -> dict:
        funcs = self.data["functions"].values()
        total = len(self.data["functions"])
        passed = sum(1 for f in funcs if f.get("success"))
        comps = {f.get("component", "") for f in funcs if f.get("component")}
        return {"total": total, "passed": passed, "failed": total - passed,
                "components": len(comps)}

    def table(self, component: str | None):
        rows = []
        for f in self.data["functions"].values():
            if component and f.get("component") != component:
                continue
            rows.append(f)
        return rows


def _b(v: str | None) -> bool:
    return str(v).lower() in {"1", "true", "yes", "pass", "y"}


def cmd_record(a):
    s = Session(a.file)
    s.record({
        "address": a.address,
        "function_name": a.name or "",
        "component": a.component or "",
        "success": _b(a.success),
        "rounds_used": a.rounds,
        "verdict": a.verdict,
        "objective_verdict": a.objective,
        "parity_status": a.parity,
        "note": a.note or "",
    })
    print(f"recorded {norm_addr(a.address)} ({'PASS' if _b(a.success) else 'FAIL'})")
    return 0


def cmd_summary(a):
    s = Session(a.file)
    su = s.summary()
    print("RE Progress Summary")
    print("=" * 40)
    print(f"Total targets:    {su['total']}")
    print(f"Passed:           {su['passed']}")
    print(f"Failed:           {su['failed']}")
    print(f"Components:       {su['components']}")
    return 0


def cmd_table(a):
    s = Session(a.file)
    rows = s.table(a.component)
    print("| Address | Function | Comp | Status | Rounds | Parity | Obj |")
    print("|---------|----------|------|--------|--------|--------|-----|")
    for f in rows:
        st = "PASS" if f.get("success") else "FAIL"
        print(f"| {f.get('address','')} | {f.get('function_name','')} | "
              f"{f.get('component','')} | {st} | {f.get('rounds_used','')} | "
              f"{f.get('parity_status') or '-'} | {f.get('objective_verdict') or '-'} |")
    return 0


def cmd_attempted(a):
    return 0 if Session(a.file).is_attempted(a.address) else 1


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__,
                                 formatter_class=argparse.RawDescriptionHelpFormatter)
    sub = ap.add_subparsers(dest="cmd", required=True)

    r = sub.add_parser("record")
    r.add_argument("--file", required=True)
    r.add_argument("--address", required=True)
    r.add_argument("--name", default="")
    r.add_argument("--component", default="")
    r.add_argument("--success", default="false")
    r.add_argument("--rounds", type=int, default=0)
    r.add_argument("--verdict", default=None)
    r.add_argument("--objective", default=None)
    r.add_argument("--parity", default=None)
    r.add_argument("--note", default="")
    r.set_defaults(fn=cmd_record)

    for name, fn in (("summary", cmd_summary), ("table", cmd_table)):
        p = sub.add_parser(name)
        p.add_argument("--file", required=True)
        if name == "table":
            p.add_argument("--component", default=None)
        p.set_defaults(fn=fn)

    at = sub.add_parser("attempted")
    at.add_argument("--file", required=True)
    at.add_argument("--address", required=True)
    at.set_defaults(fn=cmd_attempted)

    a = ap.parse_args()
    return a.fn(a)


if __name__ == "__main__":
    sys.exit(main())