
dany331x/tracebrief

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

dany331x/tracebrief

Turns REA evidence bundles into structured, evidence-first reports.
Why it is listed: MIT-licensed reporting tool.
See the original source for installation instructions.
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.
npx rea-agents@latest setup --client opencodeTraceBrief itself has zero dependencies (no dependencies or
devDependencies; nothing to install).
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.
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.
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:
~/.config/opencode/commands/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.
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.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 |
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:
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.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.
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 caughttest/contracts.test.mjs — asserts every code in the tables above is actually
reachable, and that no code emitted by src/ is missing from this READMEtest/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 alltest/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 warningnpm 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:
kind: "unknown" claim may cite nothing, and no other kind maykind is always rendered; unresolved citations are marked (missing)--allow-invalid still runs the integrity rules and stamps a visible banner--force, and preserves the command verbatim otherwiseraw_result never reaches the reportNot 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.
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.
fixtures/clipnote, a small Electron fixture applicationfixtures/clipnote-bundle.json (2.3 MB export; 3 records —
analyze_javascript_application, trace_application_feature,
trace_javascript_semantics)fixtures/report.clipnote.jsonreports/clipnote-residual-unknowns.md — the same investigation's open questions.
fixtures/clipnote-unknowns-bundle.json (7 KB; 4 records from
record_unknown and update_unknown, plus 4 unknown-history entries)fixtures/report.clipnote-unknowns.jsontarget_digest_missingunk_ 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.
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Data.dllfixtures/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)fixtures/report.system-data.jsonThat 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.
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.raw_result
payloads. Commit them only if they are safe to publish; TraceBrief never
copies them into the report./tracebrief command plus validator (current).tracebrief custom tool for OpenCode (.opencode/tools/) so validation
happens inside the tool call instead of as a separate step..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
MIT