All posts

Releases

Shipmoor Community CLI v0.2.1: a scan output you can read in five seconds

v0.2.1 redesigns the scan output around the moment a reviewer reads it. A one-line verdict, a one-line context, findings grouped by file with blockers first, inline evidence and fixes, and a footer that tells you exactly what to type next. Plus a progressive explain view and color that means something.

Shipmoor Community CLI v0.2.1: a scan output you can read in five seconds cover image

The v0.2.0 launch of Shipmoor Community CLI two weeks ago gave the tool its rule catalog, its scan modes, its CI gate, and its output contracts. What it did not give it was an output you could read.

The old human output was a paragraph and then a list:

Review readiness: Needs work. 5 findings on your change (2 high, 2 medium, 1 low).
Project context: pyproject.toml (8 deps), requirements.txt (12 deps). Scanning 1 files.
Scope: --changed · 1 file
  src/billing/webhook.py
Shipmoor scanned 1 files and found 5 findings.
Gate threshold: high
high     [would block] src/billing/webhook.py:4 python.phantom_import - Package 'stripe-retry' does not exist on PyPI.
  Recommendation: No package named 'stripe-retry' exists on PyPI. Ask the agent to use a real package or remove the import.
high     [would block] src/billing/webhook.py:5 python.phantom_import - Package 'requests' is imported but not declared in requirements.txt or pyproject.toml.
  Recommendation: 'requests' is used but not declared in requirements.txt or pyproject.toml. Add it or remove the import.
...

It worked. It was honest. It was also a wall of text that did not respect the fact that the person reading it has about five seconds before they switch tabs. If you wanted to know “is this blocked, and what do I type next?” you had to read seven lines to find out.

v0.2.1 fixes that. The scan output is now a one-line verdict, a one-line context, findings grouped by file with the blockers floated to the top, and a footer that names the exact next command. Color is intentional and means something. The explain command grew its own compact view. Nothing about the underlying rules, JSON contract, SARIF emission, or exit codes changed — this is a UX release, end to end.

curl -fsSL https://dl.shipmoor.dev/install-community-cli.sh | bash
shipmoor version
# shipmoor 0.2.1

The shape of the new output

Here is the same scan, against the same fixture, in v0.2.1:

 Needs work - 1 of 3 findings block review
none detected · 1 file · gate high · degraded resolvers
 blocks the gate  ·   informational
────────────────────────────────────────────────────────
app.py · 3
 high     :3  phantom import  python.phantom_import
    Local module 'imaginary_shipmoor_package' is referenced but no file matches under PYTHONPATH.
 'imaginary_shipmoor_package' looks local but does not resolve from project module paths. Add the file, fix PYTHONPATH, or remove the import.
 medium   :6  empty body  python.placeholder.empty_body
    `placeholder` - Function 'placeholder' has no meaningful implementation.
 Either remove the function or implement it before merging.
 medium   :10  mutable default  python.quality.mutable_default
    `mutable` - Function 'mutable' uses a mutable default argument.
 Replace `def mutable(items=[])` with `def mutable(items=None)` and initialize inside the function body.
────────────────────────────────────────────────────────
 gate fail · 1 high blocks at threshold "high"  exit 1
 fix the 1 blocker, then re-run  shipmoor scan tests/fixtures/python_only --fail-on high
 drill into one  shipmoor explain SHM-b00b9982e581ed39
2 medium won't block - worth a look.

The output is five blocks. Each one earns its space.

1. Verdict. One line, one glyph, one phrase. ✓ Ready (no findings), ○ Needs a look (findings, none above the gate), ✗ Needs work (at least one blocker). The count phrase tells you the same thing twice on purpose: N of M findings block review. On the screen the glyph is colored — green check, amber circle, red cross — so peripheral vision is enough to know whether to read further.

2. Context. One line. What manifests Shipmoor found, how many files it scanned, the active gate threshold, and degraded resolvers when no manifest is detected. This is the line that answers “did Shipmoor know what it was looking at?” before you spend time reading findings.

3. Legend. One line. ⊘ blocks the gate · ○ informational. The glyphs are not decoration — they replace the [would block] prefix from v0.2.0 with a single character at the start of every finding, so the gate impact is unambiguous without re-reading the threshold.

4. Findings, grouped by file, blockers first. The old format printed findings in severity order across the whole report, which meant a six-file diff would scatter blockers across the page. v0.2.1 groups by file and floats files with blockers to the top of the report. Inside each file, blockers come first and the rest are ordered by severity. Every finding gets three short lines: the header (⊘ high :3 phantom import python.phantom_import), the evidence (the offending source line in backticks), and the fix (→ Replace ...). The header is one row deep so you can scan a screenful of findings without scrolling.

5. Footer. This is the part that changed the most. The old output ended where the findings ended. The new output tells you:

  • gate fail · 1 high blocks at threshold "high" exit 1 — yes or no, what would block, and what the exit code will be.
  • → fix the 1 blocker, then re-run shipmoor scan tests/fixtures/python_only --fail-on high — the exact command, with the actual target you scanned, ready to copy.
  • → drill into one shipmoor explain SHM-b00b9982e581ed39 — the ID of the first blocker, paired with the command that explains it. No more “what was the syntax for explain again?”
  • 2 medium won't block - worth a look. — a one-line acknowledgement that non-blocking findings exist and are worth coming back to.

The clean-scan version is one paragraph:

 Ready - no findings on your change
none detected · 1 file · gate high · degraded resolvers
────────────────────────────────────────────────────────
 gate pass · 0 blockers  exit 0

That is what you want a passing CI run to look like in a build log. No noise. No “Shipmoor scanned 12 files and found 0 findings.” Just: ready, here is what I knew, exit 0.

Color that means something

v0.2.0 had color support, but everything that was not the severity word was the same gray. In v0.2.1, color carries information.

  • Red is reserved for things that block the gate. The verdict glyph on ✗ Needs work, the glyph on each blocker, the severity word for critical and high, the ✗ gate fail line in the footer. If you see red anywhere, something blocks.
  • Amber marks medium severity and the ○ Needs a look verdict — findings that need attention but won’t fail the gate.
  • Blue is low severity.
  • Teal marks the arrows that introduce a recommendation or a next-step command. The arrow is the thing your eye should land on; the command is what you copy.
  • Dim gray is metadata: the project context line, file paths in the file-group headers, the :N line numbers, the rule IDs in the right margin. It’s there if you need it; it doesn’t compete for attention.

The palette is automatically suppressed when stdout is not a TTY (CI logs, pipes), when NO_COLOR is set, or when --no-color is passed — CI build logs stay readable on grep, and the same scan in your terminal lights up.

A progressive view for shipmoor explain

shipmoor explain used to print the same flat block of fields a JSON dump would: severity, rule, message, root cause, recommendation, location. v0.2.1 reorganizes it into the same grammar as the scan output:

 high · phantom import  python.phantom_import
app.py:3 · SHM-b00b9982e581ed39 · confidence high · phantom_dependency
────────────────────────────────────────────────────────
why
  Local module 'imaginary_shipmoor_package' is referenced but no file matches under PYTHONPATH.
root cause
  No matching local module file could be resolved from project paths.
fix
 'imaginary_shipmoor_package' looks local but does not resolve from project module paths. Add the file, fix PYTHONPATH, or remove the import.
evidence
  import_name: imaginary_shipmoor_package
  registry_lookup: not_applicable

Same glyphs, same color rules, same separator. The page reads top-to-bottom: title, location, why, root cause, fix, evidence. If you only have time for the first three lines, you know what the finding is, where it lives, and why the rule fired. If you have more time, you keep reading until you hit the field that answers your question.

The evidence block is sorted and serialized deterministically, so a finding always renders the same way — important when you paste explain output into a PR comment and someone else needs to reproduce what you saw.

What is exactly the same

It is worth being explicit about the lines v0.2.1 did not redraw, because they are the load-bearing parts of the tool:

  • The rule catalog. 30 rules, four languages, five categories. Run shipmoor rules to list them. No new rules in this release; severities unchanged.
  • The JSON output (schema_version: shipmoor.scan.v1). Same fields, same stable fingerprint hashes, same subtype axis on phantom-import findings. A script that consumed v0.2.0 JSON will consume v0.2.1 JSON.
  • SARIF 2.1.0. Same partialFingerprints.shipmoorFingerprint, same severity-to-level mapping. The GitHub Actions recipe and the Security tab integration are unchanged.
  • Exit codes. 0 clean, 1 over threshold, 2 usage error, 3 unexpected failure. CI gates that worked yesterday still work today.
  • Scan modes. shipmoor scan <path>, --changed, --staged, --diff <range>, --patch <file>. Same five modes, same change_status classification on --diff and --patch.
  • Monorepo-aware resolution. Nested package.json / pyproject.toml / requirements.txt / go.mod files are still discovered and used as resolution contexts.
  • Patch ↔ changed parity. A --patch scan of an unapplied diff and a --changed scan of the same change still produce identical fingerprints.

If you are pinning Shipmoor in CI, this is a safe upgrade. The output your humans read got better; the output your machines parse did not move.

If there is one piece of this release worth lingering on, it is the footer. It is the answer to a question we kept hearing from people running the tool for the first time: I see the findings. What do I do?

✗ gate fail · 1 high blocks at threshold "high"  exit 1
→ fix the 1 blocker, then re-run  shipmoor scan tests/fixtures/python_only --fail-on high
→ drill into one  shipmoor explain SHM-b00b9982e581ed39
2 medium won't block - worth a look.

The footer is built from the actual run. The rerun command is the actual target you scanned, with the actual --fail-on you used. The explain line names the actual ID of the first blocker. The won't block - worth a look line is suppressed if there are no non-blocking findings. Everything is verbatim copy-paste.

The CLI is, at this point, telling you what to do next. That is the thing we wanted v0.2.0 to do and didn’t quite. v0.2.1 does.

Try it

curl -fsSL https://dl.shipmoor.dev/install-community-cli.sh | bash
cd path/to/your/repo
shipmoor scan --changed

If you have an agent-authored change sitting in your working tree right now, that’s the run we’d most like to hear about. What did Shipmoor catch? What did it miss? What part of the new output saved you a step, and what part still made you squint? The whole point of an MVP release cadence is that the next version is better than this one because the people running it told us what we got wrong.

For the longer story behind the rules, the scan modes, and the CI gate, the v0.2.0 announcement is still the canonical reference: Introducing Shipmoor Community CLI. For the full architecture — every stage, rule, and output, with diagrams — see How the Shipmoor scan works. The getting-started guide has been updated to v0.2.1: Community CLI.

  • From the shipmoor.dev Engineering Team with love ❤️

Contact sales

Our team can help with custom support, team rollouts, and self-hosted deployments. Or to get started now, explore our self-serve plans.