SnagBadgerA little help for every snag.

Portable reports and attempt evidence#

The shared report decision is the canonical product decision. This implementation supplies portable data and deterministic processing, independently of React, the browser, and a server.

Working example#

Use the pinned toolchain and run:

pnpm install --frozen-lockfile
pnpm example:report

Open dist/report-example/index.html directly. The example promotes a comment on a passing test into a bug report, keeps the actual detour, and redacts a diagnostic value. Its data is illustrative; no browser is recorded and no ticket is created. report.json contains machine-readable data. A bundle with file evidence also contains an attachments/ directory. Copy the whole directory to reopen offline.

Supported entries#

EntryResponsibility
@snagbadger/capturePortable evidence and recorded-interaction types; no capture engine yet
@snagbadger/reportingValidate, create, revise, and remove evidence from bug reports
@snagbadger/reporting/bundleExplicit sanitization, portable export/import, offline viewer, provider delivery
@snagbadger/agent-reportingTest-definition snapshots, retained attempts/comments, report promotion, capture checkpoints, regression-readiness assessment

Functions-only consumers do not import React, CSS, DOM globals, Node libraries, or the optional bundle/viewer code. All entries use the repository's shared package version. Serialized report/run/bundle formatVersion: 1 is a data-format version, independent of package releases; unsupported versions fail explicitly.

prepareReportExport(report, options) reviews narrative and attachment bytes once and returns a validated manifest plus attachment files. It performs no viewer rendering. Bug-report bundles and Test-run exports consume this shared preparation while retaining their own report models. Binary redaction, omission, or unavailable attachment proof clears reproduction and step verification just as narrative sanitization does.

Reports and edits#

createBugReport({ id, at, ...content }) accepts incomplete content. Missing expectations, outcomes, and executable reproduction are null; missing narrative text is empty. Hosts supply IDs and ISO timestamps. Reproduction steps are authored instructions; recorded interactions are observations. Evidence can contain structured JSON, an attachment descriptor, or an explicit unavailability reason.

parseBugReport(unknown) validates and detaches the complete report. It rejects unknown fields, duplicate identities, dangling evidence/interaction references, invalid readiness claims, non-JSON diagnostics, and excessive JSON nesting. Unknown fields are rejected rather than silently losing their meaning. Credentials and individual agent/collector identities are not modeled fields.

Shared validation is available through parseReportEvidence, parseExpectation, and parseReportTimestamp. Evidence validation accepts the actual outcome, hypotheses, steps, interactions, evidence, and reproduction fields together; it checks their links without requiring a report identity. Test-run parsing uses the same rules while retaining its own lifecycle and identity checks.

invalidateReproductionVerification applies the shared verification rules to steps and reproduction data. Revisions and sanitization clear step verification; a changed script additionally becomes incomplete. A different problem clears the replay claim while preserving verified actions. These operations do not mutate the supplied state. Normal callers use revision, export, or comment promotion, which apply these rules automatically.

reviseBugReport(report, changes, at) preserves stable identity and the source snapshot, increments revision, and invalidates previous reproduction/step verification. removeReportEvidence also removes its direct links from steps and interactions. Changes do not mutate previous values. Source snapshots cannot be rewritten through the edit API; they are subject to the export review policy.

Results, comments, and promotion#

parseTestRun(unknown) snapshots developer-defined tests separately from execution attempts. appendAttempt retains earlier attempts, while addResultComment adds comments to any result. Comments may link to an attempt or describe a result that has not yet run. Their basis identifies observations, hypotheses, or context.

defaultRunPolicy(retrySummary) requires an explicit developer selection of latest, any-failure, or passed-on-retry. Comment type defaults are problem, optimization, suggestion, and context; importance defaults are low, medium, high, and critical in ascending order. Both lists are configurable. Only an explicit allowAnyCommentType permits novel AI-chosen types; importance must remain on the configured scale. The default helper is optional.

Bind both a failed-result action and the comment's Create a bug action to createBugFromResult(run, { id, at, resultId, attemptId?, commentIds? }). It creates the same editable BugReport used by manual reporting. It initially includes the entire selected attempt's evidence and interactions. Absent authored steps, it derives unverified instructions from nonempty recorded action descriptions. It never substitutes the intended test steps for the actual journey.

A comment's incidental problem does not inherit the intended test's expectation or replay-verification claim. Observation comments populate actual behavior; hypothesis comments remain hypotheses. Multiple comments from one attempt may support one report. Mixing comments from different attempts is rejected rather than silently constructing a reproduction sequence that never occurred. Selecting an earlier failure explicitly remains possible after a successful retry.

The source snapshot includes the test definition and relevant comments, with run/result/attempt/comment references. Individual agent identities are deliberately absent. Provider delivery identifies the authenticated submitting user.

Sanitization and self-contained export#

createReportBundle(report, options) requires a named sanitization policy:

  • reviewReport reviews the whole report, including source snapshots, diagnostic keys/values, filenames, and executable instructions. It must preserve identity.
  • reviewAttachment explicitly includes replacement bytes or omits each available attachment. There is no implicit pass-through of screenshot/video originals.
  • readAttachment supplies bytes by evidence ID, or null for unavailable data. Retrieval/review errors abort the export; they never trigger a raw fallback.

redactReportSecrets is an exact-string helper, including nested values and keys. It is not automatic sensitive-data detection or an image/video sanitizer. Projects own sanitization rules and binary review. A safe policy may omit binary attachments until it can review them. The API cannot independently verify that a developer-provided reviewer identified every secret.

The returned relative files contain only reviewed data and bytes. A manifest records reviewed/redacted/omitted/unavailable states without removed values. Attachment paths are generated, never taken from original filenames. Missing/oversized/omitted attachments retain an explicit evidence record. Script changes during sanitization mark executable reproduction incomplete; transformed report content does not carry stale verified instructions. Each export has its own ID and policy identifier: it is a sanitized derivative of the identified report revision, not an edit of the in-memory source report.

The format uses report.json, index.html, and optional attachments/<n>.<ext>. The host chooses directory/archive/download transport. The initial viewer is unstyled, script-free semantic HTML. It escapes untrusted text, previews only supported raster/video types, and treats other attachments as downloads. It is not the final reporter or test-run UI. The structured artifact can be read selectively without loading attachment bytes into an agent's context.

importReportBundle validates versions, paths, byte lengths, references, and the generated viewer; extra files or hidden originals are rejected. Imported bytes are copied. It never executes script fields. Byte lengths are consistency checks, not cryptographic authenticity. Import is not a trust decision and does not replace sanitization before another submission. The initial format caps JSON text at 10 million characters, JSON nesting at 50 levels, and attachment bytes at 250 MB; export options can tighten the attachment limits (100 MB per attachment by default).

Delivery and reproduction boundaries#

submitBugReport(report, options, provider) uses the same export boundary and passes the complete sanitized bundle plus readable report data to the provider. The adapter resolves environmental credentials and produces a receipt identifying the authenticated user, ticket, URL, and whether the bundle was retained. Delivery returns report identity/revision and export identity alongside that receipt. IncompleteDeliveryError.delivery preserves the created ticket reference after a partial delivery. The library does not retry automatically or synchronize edits.

Executable reproduction records a project-selected runner, script, setup-hook names, required input names, missing prerequisites, and verification evidence. Those fields are data, not authority to execute imported code. This module does not supply runners, resolve ticket URLs/IDs, restore login/backend state, or run developer hooks. Those remain with the dependent replay/provider work. Local bundles preserve the information those workflows need.

Capture flags combine independently: full video, requested screenshots, each completed step, the completed final planned step, and the actual end of an attempt. requestedScreenshots identifies every applicable checkpoint, including early failure. A runner can satisfy simultaneous triggers with one image linked to each reason. The policy helper does not itself record a browser or generate agent skills.

assessRegressionCandidate requires an established expectation, executable instructions, detection on the buggy state, a pass on the fixed state, and evidence references for both. It assesses supplied validation results; it does not generate or execute tests. Missing states keep the artifact a candidate. This is separate from reproducing a problem before a fix exists.

The Markdown test workflow now supplies catalog execution, runtime callbacks/filesystem-bridge orchestration, retained attempts, and a reviewed HTML run report with editable local bug drafts. A host coordinator still supplies the actual agent/browser tools; the bridge is not an embedded MCP client or an unattended automation runner. Runtime mappings describe its isolation and installed-capability requirements.

Live capture, the final in-app reporter, supplied Jira/Linear authentication and transport, ticket-link replay, and regression-test generation remain separate product work. No server/storage design, release, merge, or deployment is introduced by these portable report APIs.

Verification#

Run pnpm verify for the full production gate. The report/result module tests exercise incomplete and heuristic journeys, retries, independent policies, promotion, snapshot isolation, sanitization failures/omissions, import validation, and provider partial delivery. A real Chromium journey opens a bundle from disk with a screenshot, confirms no external requests, and checks literal malicious text cannot execute. Built consumer checks verify optional loading and portable types.

Export and provider delivery share one private preparation path. Delivery receives the sanitized report and its matching bundle without serializing and importing it again. Independently supplied bundles still undergo full import validation, including attachment checks and viewer verification.