Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
latest_version: 3.2.14
latest_version: 3.2.15
released: 2026-05-28
---

Expand All @@ -12,6 +12,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

## [3.2.15] — 2026-05-28 — `--help` compact-with-wrap · plugin update polish · per-command emoji · version tracking · `--json` minified

- **Break: `--help` reverts to compact layout** (command + description on the same line) — v3.2.12's blanket `next_line_help = true` pushed every arg into long format, which user testing flagged as "ดูยาก" (hard to read). For args carrying `[default]` + `[possible values]` (`-o, --output`, `--mode`, `--harness` on `skill run` / `harness run`), the description still renders inline but the bracketed value block wraps to a new indented line below. Achieved by `hide_default_value = true` + `hide_possible_values = true` + a manual `\n[default: …, possible values: …]` tail on the `help` string.
- **Polish: per-command framed-header emoji differentiated.** Pre-3.2.15 both `doctor` and `update` used 🧠 (same as the OneBrain wordmark banner above them); v3.2.15: `doctor` → 🔬, `update` → 🚀, `plugin update` → 🔄. Each command now reads at a glance and stops competing with the brand glyph.
- **Polish: `onebrain plugin update` no longer leaks the orchestrator's per-step `▸ <label>` lines above its framed report.** v3.2.13's `vault_sync::run_embedded` silenced the intro/outro frame but kept the step lines; v3.2.15 routes through new `vault_sync::run_silent` which forces the progress reporter to `io::sink()`. The framed `🔄 Plugin Update` header now appears as the FIRST thing in the report (right after the brand banner), with the animated spinner as the only progress signal during work — matches `doctor`/`update`'s established UX. `run_embedded` (intro-only suppression) removed; no remaining caller wanted the in-between mode.
- **Feat: `plugin update` now reports current + latest plugin version explicitly.** The vault-sync step row reads `vX → vY` (real update), `vX · up-to-date` (rerun on the same version), or `installed vY` (fresh install) instead of the pre-3.2.15 collapse to `done` / `skipped`. The verdict footer also surfaces the delta — `updated v3.1.3 → v3.1.4` for a real bump, `update complete · v3.1.4` for no-version-change work, `already up-to-date · v3.1.4` for idempotent reruns, and `dry-run · current v3.1.4` for `--dry-run`. JSON envelope gains `version_before` / `version_after` fields (additive — both `#[serde(skip_serializing_if = "Option::is_none")]`).
- **Break: `--json` (and `--output json`) now emits MINIFIED single-line JSON.** Pre-3.2.15 auto-prettified the JSON when stdout was a TTY, which was convenient for humans glancing at a structured response interactively but made the "copy/paste into curl / a script" path noisier. To get indented JSON now, pass `--json --pretty` (or `--output json --pretty`) explicitly.
- **Break: `--output table` and `--output tsv` removed.** Both variants fell through to the JSON encoder unchanged for every command, so the format flag silently lied — `--output table doctor` emitted the same minified JSON as `--output json doctor`. Per user testing, dropped both from the `OutputMode` enum and the `--output` value parser; the remaining set is `text` / `json` / `yaml`. If a future command genuinely needs columnar output we'll add the column extractor and renderer together with the variant.
- **Polish: `skill run --help` and `harness run --help` no longer duplicate the flag breakdown above their Options: section.** Reverted v3.2.15 round-2's parent-listing flag-per-line breakdown on `SkillVerb::Run` / `HarnessVerb::Run` — clap's renderer auto-switches to long format (option name on its own line + indented description) on the verb-level help whenever the variant's `about` contains a newline. Trade-off chosen per user testing: parent-level `skill --help` / `harness --help` Commands: rows now show a one-line summary, but the verb-level `--help` Options: section is COMPACT (matches top-level `onebrain --help`) and still wraps `[default]` + `[possible values]` to an indented next line for args that carry both.
- **Polish: positional `<NAME>` args on `skill info` / `skill show` / `skill bootstrap` (and the hidden `bundle install/show/info/init/lint/update/remove`) now carry a description in the Arguments: section.** Previously they rendered as a bare `<NAME>` with no help text; the variant doc-comment described what the verb does but didn't propagate to the positional arg.

## [3.2.14] — 2026-05-28 — `plugin update` animated spinner pacing (doctor/update parity)

- **Polish: `onebrain plugin update` now animates the three step rows with the same braille spinner + random 800–2000ms pacing that `doctor` and `onebrain update` use.** Each step paints `⠋ <label>` with the cycling spinner for the random dwell, then `\r`-clears and writes the resolved `✓ <label> <detail>` row — so the framed report reads as live work instead of an instant flash. Animation gates on a real-colour stdout TTY AND respects `--quiet` (`should_animate(mode, stdout_is_tty, cli.quiet)`); pipes / CI / structured output / `--quiet` runs all fall through to the static `_to` path unchanged.
Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ default-members = ["crates/*"]
resolver = "2"

[workspace.package]
version = "3.2.14"
version = "3.2.15"
edition = "2021"
license = "AGPL-3.0-only"
authors = ["OneBrain Contributors"]
Expand Down Expand Up @@ -83,7 +83,13 @@ pretty_assertions = "1"
mockito = "1"

[profile.release]
# v3.2.15: switched from `opt-level = 3` (implicit default) + `lto = "thin"` to
# `opt-level = "z"` + `lto = "fat"` for the v3.2.x final cut. Trims ~20–30%
# off the release binary (~5.4 MB → ~3.8–4.3 MB). The CLI's hot paths are
# I/O-bound (HTTP fetches, file IO, regex scans on note-search), so size-
# optimised codegen carries no measurable runtime hit for sub-second runs.
opt-level = "z"
strip = "symbols"
lto = "thin"
lto = "fat"
codegen-units = 1
panic = "abort"
57 changes: 20 additions & 37 deletions crates/onebrain-cli/src/banner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
//! Gating rules (see [`should_show_banner`]):
//! 1. `--quiet` always suppresses.
//! 2. Only `OutputMode::Text { color: true, .. }` shows the banner — pipes,
//! `--json`, `--yaml`, `--output table|tsv`, and `NO_COLOR`/`TERM=dumb`/
//! `CI=true`/`--no-color` all drop colour and therefore drop the banner.
//! `--json`, `--yaml`, and `NO_COLOR`/`TERM=dumb`/`CI=true`/`--no-color`
//! all drop colour and therefore drop the banner.
//! 3. Hook-protocol commands (`session init`, `checkpoint stop/reset/orphans`,
//! `qmd reindex`, and their hidden v3.0 aliases `session-init`/
//! `orphan-scan`/`qmd-reindex`) need deterministic stderr because Claude
Expand Down Expand Up @@ -374,10 +374,10 @@ impl HelpBannerEnv {
/// Suppression precedence (any one disqualifies):
/// 1. `--version` / `-V` — version-only intent, no banner.
/// 2. `--quiet` / `-q` — explicit silence.
/// 3. `--json` / `--yaml` / `--output <json|yaml|table|tsv>` — machine consumer.
/// 3. `--json` / `--yaml` / `--output <json|yaml>` — machine consumer.
/// 4. `--no-color` flag — explicit colour suppression.
/// 5. Env says monochrome (`NO_COLOR` / `TERM=dumb` / `CI=<truthy>`).
/// 6. Mode is not `Text { .. }` (Json/Yaml/Table/Tsv).
/// 6. Mode is not `Text { .. }` (Json/Yaml).
///
/// The colour bit on the resolved mode is NOT checked: `assert_cmd` pipes
/// stdout in tests, which collapses the colour bit to `false`. Forcing
Expand All @@ -398,20 +398,23 @@ pub fn should_show_banner_for_help(
if args.iter().skip(1).any(|a| a == "--json" || a == "--yaml") {
return false;
}
// `--output <fmt>` / `-o <fmt>` where fmt ∈ {json,yaml,table,tsv}.
// `--output <fmt>` / `-o <fmt>` where fmt ∈ {json,yaml}.
// v3.2.15: dropped `table` and `tsv` from the format set — both fell
// through to the JSON encoder, so the banner gate now only suppresses
// for the structured formats that actually emit non-text output.
let mut iter = args.iter().skip(1).peekable();
while let Some(a) = iter.next() {
if a == "-o" || a == "--output" {
if let Some(val) = iter.peek() {
if matches!(val.as_str(), "json" | "yaml" | "table" | "tsv") {
if matches!(val.as_str(), "json" | "yaml") {
return false;
}
}
} else if let Some(val) = a
.strip_prefix("--output=")
.or_else(|| a.strip_prefix("-o="))
{
if matches!(val, "json" | "yaml" | "table" | "tsv") {
if matches!(val, "json" | "yaml") {
return false;
}
}
Expand Down Expand Up @@ -560,17 +563,11 @@ mod tests {
assert!(!should_show_banner(&cli, &OutputMode::Yaml));
}

#[test]
fn table_mode_suppresses_banner() {
let cli = parse(&["onebrain", "vault", "current"]);
assert!(!should_show_banner(&cli, &OutputMode::Table));
}

#[test]
fn tsv_mode_suppresses_banner() {
let cli = parse(&["onebrain", "vault", "current"]);
assert!(!should_show_banner(&cli, &OutputMode::Tsv));
}
// `table_mode_suppresses_banner` / `tsv_mode_suppresses_banner` removed
// in v3.2.15 along with the `Table` / `Tsv` `OutputMode` variants — both
// formats fell through to the JSON encoder, so the format flag never
// produced the columnar / TSV output it promised. JSON / YAML still
// suppress the banner via the two tests above.

#[test]
fn mono_text_mode_suppresses_banner() {
Expand Down Expand Up @@ -990,6 +987,8 @@ mod tests {

#[test]
fn should_show_banner_for_help_output_json_suppresses() {
// v3.2.15: dropped `--output=table` / `-o=tsv` rows along with the
// removed `OutputMode` variants. JSON / YAML remain.
let args = s(&["onebrain", "--help", "--output", "json"]);
assert!(!should_show_banner_for_help(
&color_text_mode(),
Expand All @@ -1002,18 +1001,6 @@ mod tests {
&args2,
&HelpBannerEnv::default()
));
let args3 = s(&["onebrain", "--help", "--output=table"]);
assert!(!should_show_banner_for_help(
&color_text_mode(),
&args3,
&HelpBannerEnv::default()
));
let args4 = s(&["onebrain", "--help", "-o=tsv"]);
assert!(!should_show_banner_for_help(
&color_text_mode(),
&args4,
&HelpBannerEnv::default()
));
}

#[test]
Expand All @@ -1036,14 +1023,10 @@ mod tests {
fn should_show_banner_for_help_structured_mode_suppresses() {
// Even if argv has no structured flag, OutputMode could already be
// structured (e.g. consumer-side mode override) — the gate also
// suppresses for Json/Yaml/Table/Tsv modes.
// suppresses for Json/Yaml modes. (v3.2.15: dropped Table/Tsv from
// the suppression list along with the variants themselves.)
let args = s(&["onebrain", "--help"]);
for mode in [
OutputMode::Json { pretty: true },
OutputMode::Yaml,
OutputMode::Table,
OutputMode::Tsv,
] {
for mode in [OutputMode::Json { pretty: true }, OutputMode::Yaml] {
assert!(
!should_show_banner_for_help(&mode, &args, &HelpBannerEnv::default()),
"expected suppression for {mode:?}"
Expand Down
Loading
Loading