cleanlib-cli 0.1.5

Terminal interface to CleanLibrary — query dependency verdicts and scan package manifests for ALLOW / DENY / WARN signals from the terminal or CI pipelines.
# Changelog — cleanlib-cli

All notable changes to the `cleanlib-cli` tool will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this crate adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- **CLEANLIB-196 (Client-3.1) — SARIF v2.1.0 output format for `cleanlib
  verdict` and `cleanlib scan`.** New `--output sarif` variant emits the
  OASIS Static Analysis Results Interchange Format v2.1.0 log so verdict
  findings can be ingested by GitHub Code Scanning, GitLab MR Security
  Dashboard, SonarQube, and any third-party CI security dashboard that
  supports SARIF. Wire mapping: cleanlib 3-tier decision → SARIF
  `level` (`ALLOW → note`, `WARN → warning`, `DENY → error`);
  verdict-label → `ruleId`; package coordinate →
  `logicalLocations[0].fullyQualifiedName` (purl-lite
  `pkg:<eco>/<name>@<version>`); severity + composite_score + verdict_id
  surfaced under `properties` for filter-friendly consumption. Golden-file
  test in `cleanlib-cli/tests/integration/sarif_golden.rs` locks the wire
  shape byte-for-byte; regenerate via `UPDATE_SARIF_GOLDENS=1 cargo test
  -p cleanlib-cli --test integration sarif_golden`.

### Fixed

- **CLEANLIB-166 — `--output <invalid-value>` now rejected at parse-time with a
  clear error listing valid options (previously silent text-fallback).**
  Prior to this fix every `--output`-carrying subcommand (`scan`, `audit`,
  `policy preview`, `fix`) did `match output.as_str() { "json" => …, _ => text
  }` — so `cleanlib scan --output josn` (a typo) silently rendered the text
  envelope and any downstream JSON-parsing CI script would fail without seeing
  a cleanlib-level error. The `--output` flag now derives clap `ValueEnum`
  (`OutputFormat { Text, Json }`) shared across `verdict` / `scan` / `audit` /
  `policy preview` / `fix`; an invalid value short-circuits at argv parse with
  clap's canonical `error: invalid value 'X' for '--output <FORMAT>' [possible
  values: text, json]` and exit code 2. `--help` now enumerates the valid
  values per verb. Sister of CLEANLIB-132 (fail-loud on missing subcommand) —
  same "never silent fail-open" philosophy applied to invalid flag values.

### Planned
- Pre-built cross-platform binaries (Linux, macOS, Windows) so customers without a Rust toolchain can install via `curl | sh` without `cargo install`
- Homebrew tap for `brew install` on macOS

## [0.1.4] — 2026-07-02

### Added

- **App-4 (CLEANLIB-211..214) — `cleanlib fix` autonomous remediation.**
  Reads a customer lockfile (`package-lock.json`, `Cargo.lock`, `poetry.lock`,
  `go.sum`), resolves verdicts per node via `cleanlib-client`, and emits an
  upgrade plan.
  - **CLEANLIB-211** — local upgrade: DENY-tier packages with a
    `recommended_version=X` marker in `suggested_actions` become fix entries;
    `--apply` rewrites the lockfile in-place (JSON edit for npm; line-oriented
    edit for the TOML `[[package]]` shape used by Cargo/poetry; go.sum row
    rewrite). Existing lockfile is backed up (`.cleanlib-backup-<unix>`).
  - **CLEANLIB-212**`--auto-pr` emits a host-agnostic PR bundle to stdout;
    downstream drivers open the actual PR via github/gitlab/bitbucket REST.
    `--apply` is a no-op when `--auto-pr` is set.
  - **CLEANLIB-213** — risk-acceptance integration: `--risk-accept <file>`
    loads the customer's `risk_accepted:` YAML (same shape `cleanlib
    risk-accept` emits); matched packages are skipped; `expires_at` honored.
  - **CLEANLIB-214** — safety guards: dry-run is DEFAULT (`--apply` required
    to write; exit 4 when pending upgrades so pre-commit hooks can gate).
    `--allow-list <file>` restricts which packages fix can touch.
    Blast-radius block names every `file:line` the fix would rewrite.

  Lockfile parsers ported from cleanlib-vscode-extension's
  `manifestParsers/lockfiles.ts` (CLEANLIB-129 transitive scanning) so the
  extension and CLI see the same graph.

## [0.1.2] — 2026-06-12

### Fixed

- **CLEANLIB-155 — `cleanlib scan` no longer silently passes block-equivalent
  verdicts.** `scan_exit_code` matched only canonical decision strings (a bare
  `_ => {}` fallthrough → exit 0), so a `VECTOR_VERDICT` / `DM_THRESHOLD_BLOCK`
  decision let a customer's `cleanlib scan || exit 1` CI gate pass silently —
  the scan-path sister-gap of the verdict-path fix (CLEANLIB-31b). Both paths
  now share `normalize_decision`: block-labels → DENY → exit 1; unknown /
  `INSUFFICIENT_DATA` → WARN → exit 2 (fail-loud, never silent-ALLOW). The two
  paths can no longer drift apart.
- **CLEANLIB-156 — `cleanlib logout` now clears the persistent verdict cache**
  (Path-2 defense-in-depth) so a logged-out session is not served previously
  cached verdicts. Adds `PersistentCache::clear()`; idempotent if no cache exists.

## [0.1.1] — 2026-06-05

### Fixed

- **`cleanlib audit` now reaches the right endpoint.** The transport layer
  hit `/v1/customer/audit` (404 on live App) instead of `/v1/audit` (200 —
  the App's `verbs_router` mount). Verified live against
  cleanapp.clnstrt.dev 2026-06-05; the command was completely broken on
  every prior release. Fix lives in `cleanlib_client::transport::Client::audit`.

### Added

- **Leading `decision:` line** in `cleanlib verdict` text output. The
  at-a-glance signal (ALLOW / WARN / DENY) now appears as the FIRST line,
  styled via the existing `style_decision` palette (green / yellow / red
  on TTY). The detail-level `verdict:` label (VECTOR_VERDICT / ...,
  customer-facing-masked via `mask_engine_tag`) stays as the second line.
  Customers reading from a terminal can scan a wall of `cleanlib verdict`
  output and pick out DENY rows immediately.
- **Multi-line `suggested:` rendering.** Single-action verdicts still
  print inline (`suggested:        <one string>`); multi-action verdicts
  now render as a bulleted list under a bare `suggested:` header. The
  prior semicolon-joined run-on string was unreadable for the
  CLEANLIB-72 populator output (3–4 advice strings per ALLOW_BY_ABSENCE
  / policy-DENY case).
- **`cleanlib scan --packages` accepts JSON-array input.** Auto-detected
  via leading `[` character. Each element is
  `{"name": "...", "version": "..."}`. The text-only `name@version`
  per-line format remains the default. Common customer-facing producer
  paths (`npm ls --json | jq ...`, package-list outputs from CI tools)
  now work without a translation step.

### Notes

- No breaking changes to existing flag surface, exit codes, or env vars.
- `--output json` machine path unchanged; the text-mode `decision:` /
  bulleted `suggested:` adjustments are TTY presentation only.

## [0.1.0] — 2026-06-03

### Added
- Inaugural crates.io release: `cargo install cleanlib-cli`
- Ten commands: `status`, `login`, `logout`, `verdict`, `scan`, `policy preview`, `risk-accept`, `audit`, `fetch`, `config init`
- Four ecosystem wrappers: `cleanlib pip`, `cleanlib npm`, `cleanlib cargo`, `cleanlib go`
- CI-gating exit-code semantics: `rc=0` on `ALLOW`, `rc=1` on `DENY`, `rc=2` on `WARN` with `--strict-warn`
- Cached verdicts with configurable TTL (default 300 seconds)
- Per-ecosystem proxy configuration emission (`.npmrc`, `pip.conf`, Go env)
- Empty-bearer fail-loud at `login` and `config init` (rejects silent-misconfig modes)
- Engine-tag masking at render: AI-derived signal sources surface as `AI-derived` to customers
- Transactional `config init` output (no partial writes on invalid input)

[0.1.0]: https://crates.io/crates/cleanlib-cli/0.1.0