Repositions and packages the one validated, unique win so people can see it and reuse it (per the positioning review). - README: new lede leads with "tell what actually changed, not what just moved" + the live `reagraph demo` contrast (59 of 56 nodes "changed" vs 1 real change) + the zero-setup `uv tool install reagraph && reagraph demo` + a GIF placeholder (record with examples/demo.tape). The feature list stays, below the pitch. - .github/actions/reagraph-diff/ (composite action): diffs two build graphs with the byte-agnostic diff, writes a Markdown summary, and posts/updates a PR comment (stable marker, no dupes); outputs the real changed-count. Inputs base/head/by/ comment/reagraph-version. - .github/workflows/release-diff.yml: example consumer (copy into your repo) that diffs two builds on release; the in-repo default runs green against the committed real-REA fixtures, with the live "pull from REA in CI" pattern shown commented. - docs/GITHUB_ACTION.md: usage, both modes (pre-exported graphs vs live pull), inputs/outputs, PR-comment behavior. - README "## Diff your releases in CI" section links it: "a bot comment on every release — the real change, not the thousands of phantom ones." Validated: the exact `reagraph diff` the action runs exits 0 on the fixtures (+4 nodes added / 2 rewired on the added-module build); all workflow YAML parses. Co-Authored-By: Claude Opus 4.8 <[email protected]>
12 KiB
REAgraph
Tell what actually changed between two builds — not what just moved.
Rebuild an app and a reverse-engineering engine reports almost everything changed, because per-build ids churn. REAgraph reports the one thing that actually changed:
$ reagraph demo
REA raw id-diff : 59 of 56 nodes "changed" <- the #4520 problem
reagraph diff : 1 changed
+ endpoint "network endpoint" src/main.js
That's two real REA analyses one build apart, diffed by meaning instead of by byte offset — precision/recall/F1 = 1.00, zero spurious, validated to 14k nodes. Try it with zero setup:
uv tool install reagraph && reagraph demo
REAgraph is the persistent-memory + structural-diff layer that sits alongside a reverse-engineering engine. It folds one-shot findings into a durable graph (so you and your agent never reverse the same thing twice), diffs two builds by structure, and attests a shipped artifact against its source. It composes REA (agent-driven analysis, MIT) and graphify (source → knowledge graph, Apache-2.0), and vendors no code from either — it is not affiliated with or endorsed by the REA project.
Status: early but real. 1300+ tests, works on real REA output, CI on Python 3.9–3.13. Live analysis runs REA as a separate process —
reagraph setup-reagets it in one command (seedocs/LIVE_PULL.md,docs/DESIGN.md).
Why
Reverse-engineering tools are brilliant sensors but they have three holes:
- No memory. Findings live in a chat session and evaporate. Every session re-pays the full (slow, token-heavy) cost and rediscovers the same facts.
- No reliable structure/diff. Version-diffing by byte location reports "everything changed" when code merely moved (REA #4520: a real app diff came back 0 unchanged because bytes shifted).
- Unamortized cost. Every analysis pays full price; nothing compounds.
REAgraph plugs those three holes: persistent queryable memory, structural (byte-agnostic) identity & diff, and a store whose value compounds as findings accumulate.
What works today
reagraph add— merge a reverse-engineering finding (REA JAG JSON, or any node-link graph) into a persistentreagraph.json, with dedup and confidence upgrade (a later observed fact promotes an earlier inferred one instead of duplicating it).reagraph diff A B— a structural diff of two graphs by node/edge identity, immune to byte relocation. This is the fix for the #4520 class of bug: a module that only moved in the file reads as unchanged.reagraph summary— a bounded map of a graph of any size (god nodes + clusters), so a gigabyte-scale analysis becomes a short overview an agent can actually read.reagraph coverage— an honest verification report: what is observed vs only inferred vs unverified, with the frontier of "probe here next" and the explicit reminder that absence of a finding is not proof of absence.reagraph stats— totals and confidence breakdown of the accumulated graph.reagraph pull <target>— connect to REA (spawned vianpx rea-agents --mcp, overridable with$REAGRAPH_REA_CMD), analyze a target, and fold each finding into the graph as it arrives. REAgraph is an MCP client of REA; it vendors no REA code. Degrades gracefully when REA ormcpis absent (usereagraph add), and when REA itself returns an analysis error it reports it and records nothing rather than folding a placeholder. A content-addressed cache (keyed by the artifact's hash + analysis scope) serves a repeat analysis with no REA call (--no-cache/--refreshto override). The cache is local today and the backend is pluggable so a shared tier can drop in later.reagraph cache stats|clear— inspect or wipe the local analysis cache ($REAGRAPH_CACHE, default~/.cache/reagraph).reagraph attest --source src.json --shipped shipped.json— does the shipped artifact match the source? Structurally aligns a graphify source graph with a REAgraph shipped-artifact graph (name match where symbols survive, Weisfeiler- Lehman fingerprints where they do not, carrying confidence and abstaining when unsure), then reports the shipped-but-unexplained residual (the supply-chain signal) and a confidence-weighted integrity score. High-signal triage, not an oracle: "unexplained" means "could not confidently match", not "malicious". Alignment uses seed-propagation to recover structure on stripped binaries, still abstaining on ties.reagraph doctor— preflight the livepull: checksmcp,node/npx, the resolved REA command, andgraphify, with actionable fixes. A clean doctor means prerequisites are present, not that a real REA run was verified.
The analysis cache backend is pluggable: reagraph.remote_cache.RemoteCacheBackend
is a shared-tier client (read-only by default so you benefit from others'
public analyses without uploading; opt-in push is gated so private artifacts
never leak; fail-open). Compose it local-first with TieredBackend (warms the
local tier on a remote hit). See docs/CACHE.md.
reagraph serve-cache— run the shared-cache server the client above talks to: a stdlib HTTP service over the same content-addressed store, with public read / token-gated write (tokens from the environment, never argv), a sha256 key allowlist (its own path-traversal defense), an upload size cap, and cache-entry-shape validation. See docs/CACHE_SERVER.md and a reference Docker/Fly deployment in deploy/.reagraph serve— run the REAgraph MCP server (remember/recall/diff/stats) so any agent uses REAgraph as its reverse-engineering memory (needs themcppackage).reagraph.bridge— fold a live stream of findings in incrementally, saving after each one (crash-safe) and skipping a bad finding instead of aborting the run (so one malformed symbol never discards the whole analysis).
Install
uv tool install reagraph # the reagraph CLI, isolated, on your PATH
uv tool install "reagraph[live]" # + mcp, for the live pull / serve / compare paths
reagraph install-skill # install the /reagraph agent skill into ~/.claude/skills
reagraph setup-rea # build a local pinned REA image (no external REA repo needed)
REAgraph runs REA as a separate process and vendors no REA code; reagraph setup-rea builds a pinned REA Docker image locally (from a Graphify-Labs mirror of
REA, MIT) and prints the exact REAGRAPH_REA_CMD — so you never visit the REA repo
or hand-assemble a docker command.
No install needed to try it: uvx reagraph diff a.json b.json. Prefer pip? pip install "reagraph[live]". Development from a checkout: uv pip install -e ".[live]".
Quickstart
# Fold a REA finding (or any node-link graph) into persistent memory
reagraph add finding.jag.json # writes/updates ./reagraph.json
reagraph add more_findings.jag.json # merges, dedups, upgrades confidence
reagraph stats # nodes/edges/confidence breakdown
# Structural version diff (byte-agnostic)
reagraph diff app-v1.json app-v2.json # added / removed / rewired, not "0 unchanged"
Query/path/explain over the accumulated graph are delegated to graphify when it is installed (the graph is written in graphify's node-link format):
graphify query "where is the license checked" --graph reagraph.json
Use it as a skill (agents talk to it directly)
A ready-made agent skill ships in skills/reagraph/.
Drop it into a Claude Code / agent setup (or copy to ~/.claude/skills/reagraph/)
and the agent learns the reverse-engineering loop: probe with REA, fold findings
into memory, recall before re-probing, diff builds by meaning, and attest shipped
vs source. Pair it with the MCP server so the agent has live remember / recall
/ diff / stats tools over a persistent graph:
reagraph serve --graph reagraph.json
# MCP client: { "mcpServers": { "reagraph": { "command": "reagraph",
# "args": ["serve", "--graph", "reagraph.json"] } } }
How it fits
agent (Claude Code / Codex / opencode)
| asks REA to analyze a region
v
REA --(finding: evidence + epistemic state)--> reagraph add
| |
| reagraph.json (persistent memory)
| |
'---- query/path/explain <---- graphify <--------'
The agent probes with REA, folds each finding into REAgraph, and queries the accumulated graph before paying for another probe. Cost stops evaporating and starts compounding.
Benchmarks
Reproducible harnesses in bench/ (python bench/bench_*.py). Numbers
below are from this machine; they are synthetic and machine-dependent, and the
REA-call latency in the cache benchmark is modeled (real REA analyses take seconds
to minutes), so treat them as orders of magnitude, not guarantees.
- Diff is byte-agnostic (the #4520 win) -- on REAL REA output. Measured on
real
rea-agentsgraphs of an Electron app (bench/bench_diff_quality.py, fixtures inbench/fixtures/diff/real, generated viaexamples/rea-docker): REA's node ids are per-build, so a naive id-keyed diff reports 73-80 of 56 nodes changed on every build-pair. Matching by build-stable signature,reagraph diffreports a reformatted-but-identical build as 0 changed and each real mutation (added endpoint / renamed function / removed handler) as exactly its true delta -- precision/recall/F1 = 1.00, noise floor 0.00, zero spurious changes, zero data loss. - Scales to a real large app. Analyzed a real 14,344-node / 14,593-edge
application (webpack + its full dependency tree, ~28 MB ASAR) end to end through
reagraph pull. REA's MCP stdio transport caps the response, so large apps use the CLI-JSON transport (--rea-cli-cmd); the adapter maps the 14k-node graph in well under a second,reagraph summaryrenders it as a bounded overview, and the signature diff recovers an injected change from full node-id churn at that scale in ~60 ms. - Alignment abstains instead of mis-matching. Measured against generated ground truth: precision 1.00 both when symbols survive and when the binary is stripped; recall 1.00 with symbols, 0.95 stripped (structure-only). All injected nodes land in the residual (5/5 stripped, 6/6 tampered). Clean vs tampered integrity separation ~0.10 (0.99 vs 0.89).
- Summary stays bounded. A 100k-node graph summarizes to ~1.4 KB (god nodes capped, clusters capped) in ~0.2 s.
- Scale. merge ~2-4M nodes/s, diff ~1-3M nodes/s, summarize ~0.5-0.9M nodes/s; align ~0.1M nodes/s (heavier: Weisfeiler-Lehman + propagation).
- Cache. A repeat analysis is served with no REA call: a hit costs ~90µs vs re-running the analysis. At a modeled 30 s/analysis over 10 repeats that is ~270 s saved; the win compounds across sessions, teammates, and CI.
Roadmap
See docs/DESIGN.md. The MCP server, live-ingest from REA, and the
shared analysis cache (client + reagraph serve-cache server, keyed by artifact
hash) are in the tree. Near-term: demand-scoped re-analysis, a hosted community
cache instance, and harder attestation on stripped native binaries.
Diff your releases in CI
A reusable GitHub Action runs the byte-agnostic diff on two build graphs and surfaces the result — a job summary plus, on pull requests, a bot comment that updates in place: a bot comment on every release: the real change, not the thousands of phantom ones. It works on pre-exported graphs (no REA needed) or on graphs pulled live from REA in CI.
- Action:
.github/actions/reagraph-diff - Example consumer workflow:
.github/workflows/release-diff.yml - How to use it in your own repo: docs/GITHUB_ACTION.md
License
MIT — see LICENSE. REAgraph is an independent project and is not affiliated with REA (morluto/rea) or its authors; see NOTICE.