Skip to content

Output formats

cofferdam check ships four rendering modes. Pick by --format=<text|json|compact|sarif>.

--robot flag: defaults to --format=json when no --format is set; otherwise the explicit --format wins. --robot does nothing else — it does not set --quiet, does not suppress ANSI (there is none), and does not change exit-code behavior. Its only effect is the format default. The idiomatic AI-agent invocation is --robot --format=compact, which combines the intent signal with the most token-economical output.

FormatAudienceSchemaByte-economy*
textHumans, terminal output (default)Free-form, decorative1.00× (baseline)
jsonCI pipelines, full-fidelity toolingStable, documented1.68× of text
compactAI agents, prompt context-shovellingStable, line-oriented0.94× of text, 0.56× of JSON
sarifGitHub Code Scanning, Azure DevOps, GitLab, SonarQube, VS Code Sarif ViewerOASIS SARIF 2.1.0 (fixed)larger than json (rules table + envelope overhead)

* Measured on a 509-finding run against bestefforttools. Numbers will shift as checks land or messages change; the relative ordering is what matters.

text format

Default. Findings grouped by category, priority-sorted within each. Decorated with category headers and a trailing summary line. Use for humans reading reports in a terminal.

text
── Warning ───────────────
  [ 15] [    high] src/auth.ts:42:7  use `===` instead of `==`  (Warning.TripleEquals)

1 finding(s)

Honors --quiet (suppresses the summary line and the "no findings" line). Emits no ANSI escape codes today, so NO_COLOR is trivially honored.

json format

Stable schema, machine-readable. The full contract is the canonical source for tooling integrations. Pretty-print with --pretty.

jsonc
{
  "summary": {
    "total": 1,
    "by_category": { "warning": 1 }
  },
  "findings": [
    {
      "id": "Warning.TripleEquals",
      "category": "warning",
      "priority": 15,
      "severity": "high",
      "file": "src/auth.ts",
      "line": 42,
      "column": 7,
      "start_byte": 800,
      "end_byte": 806,
      "message": "use `===` instead of `==`"
    }
  ]
}

Field reference

Every field listed below is stable — field names and types are part of the contract. Adding new fields is non-breaking; renaming or removing fields, or changing a field's type, requires a major version bump.

Top-level object

FieldTypeAlways presentDescription
findingsarrayYesOrdered array of finding objects, sorted by priority descending then check ID ascending. Empty array when no findings.
summaryobjectYesAggregate counts for the run.

summary object

FieldTypeAlways presentDescription
totalintegerYesNumber of findings in the findings array. When --max-issues truncation is active, this equals the truncated count (not the full count — see truncated_from).
by_categoryobjectYesMap of lowercase category name → finding count. Only categories with at least one finding appear; zero-count categories are omitted. Keys are a subset of: consistency, design, readability, refactor, warning.
newintegerNo — baseline onlyCount of findings not matched by the active baseline (i.e., genuinely new since cofferdam baseline write). Omitted when no baseline is active.
baselinedintegerNo — baseline onlyCount of findings matched by the active baseline (these were present when the baseline was written and are acknowledged). Omitted when no baseline is active.
truncated_fromintegerNo — truncation onlyTotal findings produced before --max-issues capping. Always strictly greater than total when present. Omitted in the common case (no truncation) so the byte output is identical to pre-truncation cofferdam.

Per-finding object (each element of findings)

FieldTypeAlways presentDescription
idstringYesDotted check ID, e.g. Warning.TripleEquals. Stable — safe to use as a map key or filter.
categorystringYesLowercase category: consistency | design | readability | refactor | warning.
priorityintegerYesComputed sort priority in the range -20..=20. Higher value = surfaces first. Not configurable; derived by the engine.
severitystringYesConfigured severity: info | low | medium | high | critical. Matches --fail-on=<level> threshold values.
filestringYesPath to the file containing the finding. Forward-slash normalized (even on Windows) so it is safe to use as an editor link or CLI argument on any platform.
lineintegerYes1-based line number of the finding's primary span.
columnintegerYes1-based column number of the finding's primary span.
start_byteintegerYesByte offset of the span start into the file's UTF-8 text. Useful for span-based autofix or highlighting without re-parsing.
end_byteintegerYesByte offset of the span end (exclusive).
messagestringYesHuman-readable finding description. May contain backtick-quoted identifiers.
relatedarrayNo — multi-location onlyAdditional locations participating in the same finding (e.g. the other files where a duplicate export or duplicate block appears). Omitted entirely (not present, not []) when the finding is single-location. See sub-fields below.
baselinedbooleanNo — baseline onlytrue when this finding matched an entry in the active baseline; false when it did not. Omitted entirely when no baseline is active, so the JSON schema for a no-baseline run stays byte-identical to pre-baseline cofferdam.

related array element sub-fields

FieldTypeDescription
filestringPath to the related file, forward-slash normalized.
lineinteger1-based line of the related span.
columninteger1-based column of the related span.
start_byteintegerByte offset of the related span start.
end_byteintegerByte offset of the related span end (exclusive).

Optional fields appear only when relevant — summary.new / summary.baselined / per-finding baselined show up when a baseline is active; summary.truncated_from shows up only under --max-issues. Schema additions are non-breaking; field renames or removals are.

compact format

Pipe-delimited line-per-finding format. Designed for the case where an AI agent shovels findings into a prompt and pays per byte. Header line followed by one record per finding.

text
priority|severity|category|id|file|line|column|message
15|high|warning|Warning.TripleEquals|src/auth.ts|42|7|use `===` instead of `==`

Schema (stable contract)

Header is fixed at exactly eight |-separated columns:

priority|severity|category|id|file|line|column|message

The columns are stable. Adding a column would break parsers that hardcode the count, so the schema does not grow inline — see "Limitations" below for the trade-offs and what to do when you need extra fields.

Parsing rules

  • Message is the last column. Any embedded | characters in a message render naked. Use splitn(8, '|') (Rust), split('|', 8) (Python str.split), or your language's "split on first N delimiters" primitive — take the rest of the line as the message.
  • Lines are LF-delimited. Newlines inside a message are collapsed to spaces so the line-per-finding contract is unconditional.
  • Empty result is well-formed. When zero findings are emitted, the output is just the header line followed by a newline. Parsers that always read the first line for column names won't crash on a clean run.
  • Paths are forward-slashed. Same convention as the other formats — agent-friendly, copy-paste-able as an editor link.

Severity column values

info | low | medium | high | critical. Same set as --fail-on=<level>.

Category column values

consistency | design | readability | refactor | warning. Lowercased. (Or unknown for malformed check IDs — practically never happens for built-in checks.)

Limitations

Compact mode v1 does not carry:

  • Baseline tags. When a baseline is active, the per-finding baselined flag is dropped from compact output. Use --format=json if you need to know which findings are baselined vs new. The CI gate (--fail-on) still respects baselines correctly — only the display drops the tag.
  • Related spans. Cross-file findings (Design.DuplicateExportName, Refactor.DuplicateBlock) emit only the primary location in compact mode. The "also at" locations are JSON-only.
  • Truncation note. --max-issues truncates the rendered findings; compact mode does not surface the original total. Use --format=json (which adds summary.truncated_from) when truncation matters.

These are deliberate v1 cuts to keep the schema fixed at eight columns. If a future use case demands them, the path forward is a --format=compact-v2 opt-in rather than silently widening the existing schema.

Byte-economy

Measured against the same 509-finding run on bestefforttools:

  • text: 87,548 bytes (baseline)
  • json (compact, no --pretty): 146,882 bytes — 1.68× text
  • compact: 81,831 bytes — 0.94× text, 0.56× JSON

Compact's win over JSON (≈44% bytes saved) is structural: no field-name repetition, no quoting noise, no empty summary object. The win over text is small because cofferdam's text formatter is already information-dense — one line per finding with no decorative padding. The real value of compact mode is parsing simplicity for agents, not raw byte savings: splitn(8, '|') beats running a JSON parser when you're pushing context into an LLM call.

sarif format

SARIF (Static Analysis Results Interchange Format) is the OASIS-standard JSON schema for static-analysis tool output and the format GitHub Code Scanning ingests natively. --format=sarif emits a SARIF 2.1.0 document suitable for direct upload via github/codeql-action/upload-sarif — see the CI recipe. The same document is consumed by Azure DevOps, GitLab Vulnerability Reports, SonarQube, and the VS Code Sarif Viewer.

Pretty-print with --pretty (defaults to compact, single-line JSON).

bash
cofferdam check --format sarif --pretty src/

Mapping from cofferdam concepts to SARIF:

cofferdamSARIF
Issue.check_idruns[].results[].ruleId
Issue.messageruns[].results[].message.text
Issue.fileruns[].results[].locations[].physicalLocation.artifactLocation.uri (forward-slashed)
Issue.span.lineruns[].results[].locations[].physicalLocation.region.startLine
Issue.span.columnruns[].results[].locations[].physicalLocation.region.startColumn
Issue.span.start_byteruns[].results[].locations[].physicalLocation.region.byteOffset
Issue.span.end_bytestart_byteruns[].results[].locations[].physicalLocation.region.byteLength
Issue.related[]runs[].results[].relatedLocations[]
CheckMeta.idruns[].tool.driver.rules[].id (one entry per unique check id seen)
CheckMeta.explanationruns[].tool.driver.rules[].shortDescription.text and .fullDescription.text
CheckMeta.categoryruns[].tool.driver.rules[].properties.category (also surfaced as a tag)
CheckMeta.base_priorityruns[].tool.driver.rules[].properties.priority

Severity → SARIF level

SARIF has a coarser scale than cofferdam (note | warning | error | none). The fixed mapping is:

cofferdam severitySARIF level
infonote
lownote
mediumwarning
higherror
criticalerror

The level is emitted both per-rule (defaultConfiguration.level) and per-result (level) so consumers that respect either field render correctly.

Fingerprints (cross-run finding identity)

Every result carries a partialFingerprints["cofferdam/v1"] entry — a stable hex digest of (check_id, file, line, message). GitHub Code Scanning (and other consumers) use this to keep the same finding pinned to the same alert across runs even when surrounding line numbers shift. The cofferdam/v1 namespace versions the algorithm; if the inputs ever change, the version bumps so existing alerts don't all re-trigger.

Limitations

SARIF mode v1 does not carry:

  • Baseline tags. SARIF has no native field for "this finding was already present at baseline write time." GitHub Code Scanning and most other UIs track new vs. acknowledged via their own dismissal model on top of partialFingerprints — that's the SARIF-native answer. Use --format=json when you need the per-finding baselined flag in the output.
  • Truncation note. --max-issues truncates the rendered results without a separate truncated_from field. Use --format=json when truncation accounting matters.

The CI gate (--fail-on) is unaffected — it always considers the full pre-truncation set.

Picking a format

  • Local terminal usetext (default). Skip everything else.
  • CI / tooling integrations / programmatic consumersjson. Full schema, baseline-aware, related spans, truncation metadata.
  • AI-agent prompt shovellingcompact when token economy is the priority and you don't need baseline tags or related spans; json when you do.
  • GitHub Code Scanning, GitLab Vulnerability Reports, Azure DevOps, SonarQube, VS Code Sarif Viewersarif. Standard schema, no per-platform glue code.

--robot --format=compact is the canonical AI-agent invocation.

Future formats

These formats are planned and not yet implemented. Passing --format=github-annotations today will produce an error.

github-annotations (planned)

GitHub Actions supports workflow commands that annotate pull-request diffs inline. The github-annotations format will emit one line per finding in the format:

::warning file=src/auth.ts,line=42,col=7,endLine=42,endColumn=13::Warning.TripleEquals: use `===` instead of `==` to avoid type coercion

This is line-oriented and intended to be piped through echo inside a GitHub Actions step, producing annotations that appear directly in the PR file view. Like compact, it will not carry related spans or baseline tags (JSON is the right choice when you need those). Severity-to-level mapping: info::notice, low/medium::warning, high/critical::error.

MIT License