REA MCP
☰
EN
● — online · Views — times
SponsorsSubmit a case
GitHub repositories · by dany331x

TraceBrief: evidence-first reverse-engineering reports

Turns REA evidence bundles into structured, evidence-first reports.

Translated

About this case

Turns REA evidence bundles into structured, evidence-first reports.

Why it is listed: MIT-licensed reporting tool.

Setup

See the original source for installation instructions.

Original text / README

TraceBrief

Evidence-first reverse-engineering reports.

TraceBrief turns a REA investigation into a Markdown report another developer can read, reproduce and check — where every technical conclusion points at a real REA Evidence record, observations are separated from inferences, and unresolved questions stay visible.

It does not analyse anything. REA produces evidence; TraceBrief organises it and refuses to publish a claim the evidence cannot support.

REA investigation ──export_evidence_bundle──▶ bundle.json
                                               │
        agent authors claims (report.json) ────┤
                                               ▼
                        validate ──fail──▶ actionable errors, no file
                           │
                          pass
                           ▼
                     reports/<name>.md

The split matters: an agent writes claims, a deterministic script checks the citations and renders the document. A prompt alone cannot verify that ev_3f9c… exists or that a runtime claim is not resting on static evidence.

Requirements

  • Node.js 22.19+ (developed and tested on 24.21)
  • A REA MCP registration for the agent doing the authoring — for example npx rea-agents@latest setup --client opencode

TraceBrief itself has zero dependencies (no dependencies or devDependencies; nothing to install).

Upstream status

TraceBrief is standalone. Integration into REA has been proposed in morluto/rea#1142, which asks which integration shape is worth carrying rather than presenting a finished patch — REA's CONTRIBUTING.md asks for an issue before an architecture change of this size, and TraceBrief does not yet meet REA's toolchain gates (it is plain .mjs under node --test, not TypeScript under vitest).

Nothing here depends on that outcome. If REA declines it, this repository is the deliverable as-is.

Install

git clone https://github.com/dany331x/tracebrief.git
cd tracebrief
npm test

Use it as an OpenCode command:

npm run install:command     # installs /tracebrief globally
npm run uninstall:command

OpenCode discovers commands from .opencode/commands/ in the project you are working in, or ~/.config/opencode/commands/ for global use. TraceBrief is normally used somewhere other than its own repository — in the project holding the target you are investigating — and the repo copy of the command invokes node bin/tracebrief.mjs, which only resolves inside this repository. The installer therefore rewrites that one invocation to an absolute path and copies the file to ~/.config/opencode/commands/tracebrief.md. It refuses to overwrite an existing install unless you pass --force, is idempotent, and refuses to guess if the command file no longer contains the invocation it expects. Restart OpenCode afterwards.

node scripts/install-command.mjs --target <dir>   # install elsewhere
node scripts/install-command.mjs --uninstall

The repository copy at .opencode/commands/tracebrief.md also works as-is when your cwd is this project.

The command was verified by invoking it

Not by inspecting the file. opencode run "/tracebrief" in an empty directory resolved the global command and executed it. With no investigation in that session the agent exported a bundle, found zero records, and stopped — and in doing so proved three things:

  • OpenCode discovers and runs the command from ~/.config/opencode/commands/
  • the guard works: it refused to continue without a target, quoting the instruction "do not pick an example target on the user's behalf"
  • the no-fabrication rule is understood: it noted that every claim would fail validation and that the only way to make the report pass would be inventing ev_<64 hex> ids, "which the command forbids outright"

It also noticed $ARGUMENTS was empty and derived the filename question rather than guessing. That run exits non-zero, correctly: there was nothing to report on.

Usage

node bin/tracebrief.mjs validate --report report.json --bundle bundle.json [--json]
node bin/tracebrief.mjs render   --report report.json --bundle bundle.json [--out report.md] [--allow-invalid]
node bin/tracebrief.mjs index    --bundle bundle.json [--json]
node bin/tracebrief.mjs --help

Exit codes:

| Code | Meaning | | --- | --- | | 0 | Clean. Warnings may still have been printed. | | 1 | Validation errors. render wrote nothing unless --allow-invalid. | | 2 | Usage or I/O failure: missing/unreadable/invalid-JSON report or bundle, unknown command, unknown or malformed flag. |

Unknown flags, single-dash typos (-v), stray positional arguments and flags used with the wrong subcommand are rejected rather than ignored, so a typo cannot silently change what runs — or make a render write no file and still exit 0.

Try it against the committed real bundle:

npm run validate:sample            # Electron feature trace
npm run render:sample
npm run validate:sample:unknowns   # residual unknowns
npm run render:sample:unknowns
npm run validate:sample:managed    # managed .NET assembly
npm run render:sample:managed

Report schema

report.json, schema tag tracebrief/report@1. All nine keys are required.

| Field | Type | Notes | | --- | --- | --- | | title, question | non-empty string | Free prose; newlines are flattened on render | | target.name | non-empty string | Not nullable | | target.version, target.format, target.architecture, target.local_path | string or null | null renders as unknown — not collected | | target.artifact_digest | sha256 hex or null | Omitting it disables cross-artifact verification and warns | | bundle_digest | sha256 hex, or omitted | Optional. Pins the report to one exact bundle; a mismatch is an error. An explicit null is rejected, because it would look pinned while verifying nothing | | reproduction.environment | array of strings | Use [] for not collected; null is rejected | | reproduction.tool_versions[] | { name, version?, evidence_ids } | name and evidence_ids required; version must be a string if present | | reproduction.commands[] | { command, purpose?, evidence_ids } | command and evidence_ids required; purpose must be a string if present | | findings[], relationships[] | claims | May be empty arrays | | open_questions[] | { id, question, status, evidence_ids, unknown_id? } | status is one of open, investigating, blocked, contradicted. evidence_ids is required and may be [] | | summary.established | array of claim ids | Each must exist in findings or relationships | | summary.not_established | array of non-empty strings | |

evidence_ids is always required, including on a kind: "unknown" claim, where it is simply [].

A claim:

{
  "id": "f-ipc-channels-paired",
  "statement": "REA recorded three literal IPC channels and paired all three renderer transmissions with a main-process handler.",
  "kind": "derived",
  "claim_type": "structure",
  "evidence_ids": ["ev_b318dce1e595a404df70e1f22c8d16d8e1216f409f20aeeacf6ecc3bb4220779"],
  "limitations": ["Electron relationships are derived from inert syntax; runtime registration, reachability, defaults, and enforcement remain unproven."]
}

kind is observed | derived | inferred, mirroring REA's own confidence vocabulary, plus unknown for a gap that was deliberately left open.

claim_type is what the claim asserts, and drives the integrity rules:

| claim_type | Allowed when | | --- | --- | | structure | Describes the artifact's static shape, with at least one observed or derived record cited | | behavior | Describes what code does, with at least one observed or derived record cited | | runtime-behavior | Cites a record with authority controlled-replay or external-service | | source-equivalence | Cites a historical-reference record (an imported historical source tree) | | analysis-coverage | States what the analysis did or could not establish; exempt from the observed-basis rule, since reporting an evidence gap is a legitimate result |

Evidence integrity rules

Errors block rendering. Warnings inform. These are the rules in src/rules.mjs:

| Code | Level | Rejects | | --- | --- | --- | | evidence_not_in_bundle | error | A citation that is not in the bundle — the fabrication case | | evidence_subject_mismatch | error | Evidence about a different artifact than the reported target | | claim_without_evidence | error | A non-unknown claim citing nothing | | claim_requires_observed_basis | error | structure/behavior resting only on inferred records | | runtime_claim_without_runtime_authority | error | Runtime behavior asserted from static-only authority | | source_equivalence_without_historical_source | error | "This is the original source" with no historical source record | | residual_unknown_not_carried | error | A bundle unknown missing from open_questions | | target_not_in_bundle | error | A target digest absent from bundle.artifacts | | summary_establishes_unknown | error | A summary claiming a claim that is marked unknown | | bundle_digest_mismatch | error | A pinned bundle_digest that is not the supplied bundle | | target_digest_missing | warning | No target digest, so cross-artifact verification is off. Warned unconditionally, including when the bundle lists no artifacts | | evidence_subject_unavailable | warning | Cited records carry no subject, so the digest link rests on evidence_links alone | | claim_limitations_not_carried | warning | The claim quotes none of the limitations its cited records carry | | evidence_link_unresolved | warning | A cited record links to evidence outside the bundle | | command_unverified | warning | A reproduction command asserted without a citation | | tool_version_unverified | warning | A tool version asserted without a citation | | bundle_digest_unverified | warning | A pinned digest with no bundle file to compare against | | open_question_unknown_not_in_bundle | error | An open question cites a residual unknown that is not in the bundle — an invented unk_ id | | open_question_unknown_resolved | error | An open question cites a residual unknown the bundle records as resolved | | claim_statement_duplicated | warning | Two claims share a statement, so one finding is presented twice rather than corroborated twice |

Document and bundle checks are separate, structural passes:

  • Report schema (src/report.mjs): report_not_object, report_key_missing, report_key_unknown, report_schema_mismatch, text_missing, target_invalid, target_field_invalid, target_digest_malformed, report_bundle_digest_malformed, report_section_invalid, claim_invalid, claim_id_duplicated, claim_kind_invalid, claim_type_invalid, claim_limitations_invalid, claim_limitation_empty, evidence_ids_missing, evidence_ids_invalid, evidence_id_malformed, open_questions_invalid, open_question_invalid, open_question_id_duplicated, open_question_status_invalid, open_question_unknown_id_malformed, summary_invalid, summary_field_invalid, summary_claim_unknown, reproduction_invalid, reproduction_key_missing, reproduction_list_invalid, reproduction_entry_invalid, reproduction_environment_invalid.
  • Evidence bundle (src/bundle.mjs): bundle_not_object, bundle_missing_array, bundle_unknown_key, record_not_object, record_id_malformed, record_id_duplicated, record_confidence_invalid, record_authority_invalid, record_provider_missing, record_limitations_invalid, record_links_invalid, record_subject_invalid, record_locations_invalid, record_location_invalid, record_location_field_missing, record_location_kind_unknown, unknown_not_object, unknown_id_malformed.

validate --json prints every issue with its code, severity and JSON path.

What is verified, and what is not

179 tests (npm test, zero dependencies). npm test runs node --test "test/*.test.mjs"; the glob is deliberate, because bare node --test also treats the shared test/helpers.mjs module as a test file and reports a phantom 180th empty suite. Several of those files exist specifically to close gaps an audit found in this suite:

  • test/regression.test.mjs — one test per real crash, bypass or silent failure reproduced before it was fixed, including a crash the hardening itself introduced and a second review pass caught
  • test/contracts.test.mjs — asserts every code in the tables above is actually reachable, and that no code emitted by src/ is missing from this README
  • test/rules-gaps.test.mjs, test/render-gaps.test.mjs, test/cli-gaps.test.mjs — coverage for rules, renderer branches and CLI paths that previously had no assertion at all
  • test/real-unknowns.test.mjs, test/real-managed.test.mjs — behaviour proven against the real REA bundles committed here, including the revision-history rule and both directions of the subject warning

npm run test:mutations is the honesty check on all of that: it copies the project, reverts one shipped fix at a time, and fails if the suite does not notice. It runs a baseline first, because a broken test invocation would otherwise make every mutation look caught. All 23 mutations are currently caught by the test that claims to cover them.

What the suite verifies:

  • fabricated, malformed, duplicate and missing citations are rejected
  • issue paths point at the citation index the author actually wrote, including when a non-string entry sits earlier in the list
  • evidence from the wrong artifact is rejected, and omitting the target digest warns instead of silently disabling the check
  • runtime claims require runtime authority; source-equivalence requires a historical source; inference-only support for a structural claim is rejected
  • limitation acknowledgement requires a substantive quotation: unrelated text, empty strings and single characters are all rejected
  • a kind: "unknown" claim may cite nothing, and no other kind may
  • relationship claims are held to exactly the same rules as findings
  • a residual unknown cannot be invented or presented as open once it is resolved
  • one finding restated is flagged rather than counted as corroboration
  • malformed bundles and reports are reported instead of crashing
  • missing, unparseable and BOM-prefixed report and bundle files exit 2
  • unknown flags, single-dash typos, stray arguments and missing required flags are usage errors with usage text, not I/O errors
  • authored text cannot inject headings, list items, table rows or code fences — including via a lone carriage return or a backtick inside a claim id
  • kind is always rendered; unresolved citations are marked (missing)
  • every empty section says it was not collected
  • rendering is byte-identical across separate processes, not just within one tick
  • --allow-invalid still runs the integrity rules and stamps a visible banner
  • the command installer pins a CLI path that exists, is idempotent, refuses to overwrite without --force, and preserves the command verbatim otherwise
  • raw_result never reaches the report
  • the committed real REA bundle still validates after all of the above

Not verified, by design: TraceBrief can prove a citation exists. It cannot prove a sentence reads the cited bytes correctly. Prose that misinterprets evidence still passes. It also does not render Markdown itself, so "cannot inject" is asserted as a line-level property of the output, not by round-tripping a CommonMark parser. Both limits are stated here rather than implied away.

Samples

Both samples are generated from real REA 6.0.0 output, not from synthetic data.

reports/clipnote-search.md — a feature trace through an Electron app.

  • target: fixtures/clipnote, a small Electron fixture application
  • evidence: fixtures/clipnote-bundle.json (2.3 MB export; 3 records — analyze_javascript_application, trace_application_feature, trace_javascript_semantics)
  • claims: fixtures/report.clipnote.json
  • validates with 0 errors and 3 warnings, one of which says the TraceBrief render command itself is not backed by REA evidence

reports/clipnote-residual-unknowns.md — the same investigation's open questions.

  • evidence: fixtures/clipnote-unknowns-bundle.json (7 KB; 4 records from record_unknown and update_unknown, plus 4 unknown-history entries)
  • claims: fixtures/report.clipnote-unknowns.json
  • validates with 0 errors and 3 warnings, including target_digest_missing
  • this sample exists because of a bug it found: a real REA bundle keeps the full revision history of each residual unknown, so the same unk_ id appears once open and again resolved. TraceBrief now reduces unknowns to one head per id before deciding what is open, and test/real-unknowns.test.mjs proves the result is order-independent. Before that fix, a stale open revision could make the tool demand that a report carry an already-closed question.

reports/system-data-managed.md — a real managed assembly.

  • target: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Data.dll
  • evidence: fixtures/system-data-bundle.json (693 KB; 5 records — one inspect_managed_artifact record for a mixed-mode CLR assembly plus the residual-unknown registry records from the same session)
  • claims: fixtures/report.system-data.json
  • validates with 0 errors and 0 warnings

That third sample earned its place. It is a genuinely mixed bundle — one record with an artifact subject and four without — which is the only way to prove the subject rule means "no cited record has a subject" rather than "some cited record lacks one". It also exercises mixed location kinds on real data: a file-offset-range for the CLI header plus two single file-offset locations, all file-based with no memory addresses. REA classifies the assembly as managed with runtime family mixed-clr-native, because COMIMAGE_FLAGS_ILONLY is clear.

It also demonstrates the tool working against its own author: two drafted claims in the first sample were rejected as unsupported assertions about the application until they were reclassified as analysis-coverage, which is what they were.

Environment note: all three samples are static analysis only. Hopper is macOS/Linux, and no Ghidra or IDA installation was configured here, so no report makes a native claim.

Limitations

  • Evidence ids are content digests. Pin bundle_digest in the report and TraceBrief will refuse a mismatched bundle; without it, re-validating a stale report against a changed bundle succeeds and only the footer digest differs. This is not hypothetical: during development the records from a 2.3 MB export were no longer retained in the REA session by the time a later bundle was exported, which is exactly the drift the pin catches.
  • Bundles can be large (this sample is 2.3 MB) and contain raw_result payloads. Commit them only if they are safe to publish; TraceBrief never copies them into the report.
  • Reports are authored by an LLM, so wording quality is unverified. Only citations and assertion classes are machine-checked.
  • The limitation-acknowledgement rule requires quoting at least one carried limitation, not all of them: a record may carry a dozen. Every carried limitation is printed in the report regardless.
  • Native analysis paths are untested on this host (no Hopper/Ghidra/IDA).

Roadmap

  1. /tracebrief command plus validator (current).
  2. A tracebrief custom tool for OpenCode (.opencode/tools/) so validation happens inside the tool call instead of as a separate step.
  3. Upstream discussion with the REA maintainers. REA owns canonical Evidence serialization, so a bundle-to-Markdown renderer arguably belongs in REA itself; the claim model and integrity rules are the part specific to agent workflows.

Layout

.opencode/commands/tracebrief.md   /tracebrief command (repo copy)
scripts/install-command.mjs        installs /tracebrief globally
bin/tracebrief.mjs                 CLI: validate | render | index
src/bundle.mjs                     Evidence bundle reading and structural checks
src/report.mjs                     report.json schema validation
src/rules.mjs                      evidence integrity rules
src/render-markdown.mjs            deterministic, injection-proof renderer
test/                              node:test suite
fixtures/clipnote/                 Electron fixture application
fixtures/clipnote-bundle.json      real REA evidence export
fixtures/report.clipnote.json      authored claims for the sample
reports/                           generated reports

Licence

MIT

Similar projects