Every scan produces the same evidence; you choose how it’s rendered. Human output is for you, deterministic JSON is for scripts and agents, SARIF is for code scanning, and a stable four-code exit contract makes all three safe to gate a build on.
Human output (default)
shipmoor scan --changed
The default renderer leads with a one-line verdict, groups findings by file with blockers first, and ends by naming the exact command to run next (shipmoor explain <id> for the first blocker, plus the rerun command). Color is suppressed automatically when stdout is not a TTY, when NO_COLOR is set, or with --no-color.
JSON: shipmoor.scan.v1
shipmoor scan --changed --json --output shipmoor.json
The JSON report is a stable, versioned contract (shipmoor.scan.v1), and the same document the Agent Harness, the IDE extension, and your own CI scripts consume. Key fields per finding:
| Field | What it holds |
|---|---|
id | Stable finding ID (SHM-…); usable with shipmoor explain <id> --from <report> |
rule_id | The rule, e.g. python.phantom_import |
severity | critical / high / medium / low |
path, start_line | Where |
message, recommendation, evidence | The what, the fix, and the proof |
subtype | Finer classification, e.g. hallucinated_package |
change_status | introduced / modified / preexisting / unknown for diff and patch scans |
fingerprint | Stable hash for deduplication across runs |
When --json writes to stdout (no --output), stdout contains only the JSON document; diagnostics go to stderr. That discipline is what lets agents and pipelines parse the output without scraping.
SARIF for code scanning
shipmoor scan --changed --sarif --output shipmoor.sarif
SARIF 2.1.0, ready for GitHub code scanning upload. The same stdout/stderr discipline applies. See SARIF & code scanning for the workflow.
Markdown summary
For CI job pages, --markdown-summary <path> writes a human-readable digest alongside whichever main format you chose:
shipmoor scan --diff origin/main...HEAD \
--sarif --output shipmoor.sarif \
--markdown-summary "$GITHUB_STEP_SUMMARY"
The exit-code contract
Four codes, stable across versions. This is the contract the Agent Harness and CI integrations are built on:
| Code | Class | Meaning |
|---|---|---|
0 | clean | No gate fired. The report is present. |
1 | threshold | Findings met the gate. Not an error; the JSON/SARIF is still written and complete. |
2 | usage | Bad arguments or config. Error on stderr, no report. |
3 | scan failed | The scan itself crashed. Error on stderr, no report. |
Two rules of thumb for anything consuming these codes:
- Treat
1as a signal, not a failure. It means the gate worked. Parse the report and act on the findings; don’t route it to your “tooling broke” path. - Reserve error handling for
2and3. Those are the only codes where no report exists.
Authentication and allowance messages are written to stderr so they do not contaminate JSON or SARIF stdout. Claim Check is a separate command with its own exit-code contract.
Note:
shipmoor upgradehas its own, separate exit codes for install plumbing (20network,21checksum, …). See Installation. The0/1/2/3contract above is specifically thescansurface.
Next
- Gating & policy: choosing what makes exit code
1fire. - GitHub Actions: the copy-paste workflow.
- Output contracts & schemas:
scan.v1,capabilities.v1,identity.v1in full.