feat(fj): global --format/--json output flag and JSON for all commands (FJ-23) #22

Merged
David merged 1 commit from feat/global-output-format-fj-23 into main 2026-05-28 22:30:24 +02:00
Owner

Summary

Implements FJ-23: a single global output-format flag so every command can emit stable, machine-readable JSON, with unchanged default (human-readable) behavior.

  • Adds a global --format <text|json> flag (clap global = true) on App, defaulting to text and accepted in any position for any subcommand, plus a --json shorthand. --json is equivalent to --format json; combining --json with --format text is a usage error.
  • Stores the resolved format once in an OUTPUT_FORMAT OnceCell (mirroring SPECIAL_RENDER), read everywhere via crate::output_format() / crate::json_output(). No run() signature threads a new parameter.
  • Under JSON, --style is forced to Minimal and all decorative, progress, spinner, interactive-prompt, browser, and fluent-rendered output is suppressed so stdout stays valid JSON.
  • Every command except fj completion emits JSON following the issue taxonomy: reads/lists emit a record or array, mutations emit the affected resource (or { "ok": true }), local side-effecting ops emit { "path": ..., "ok": true }, browse commands emit { "url": ... } without spawning a browser, and pr diff / pr patch wrap the raw text as { "diff": ... } / { "patch": ... }.
  • Each command renders through a local serde::Serialize view (never serializing forgejo-api structs directly), following the pr_to_json precedent: RFC 3339 UTC timestamps, stable lowercase English field/enum names that are locale-independent and never routed through fluent.
  • JSON serialization lives entirely in the fj binary, preserving the fj-core / fj-client MCP-reuse contract.
  • The per-subcommand json: bool clap fields on pr status / pr search are removed and now driven by the global flag; both keep their existing schema unchanged.
  • fj completion is documented as exempt and keeps emitting the raw completion script regardless of --format.

Verification

  • cargo build, cargo clippy, cargo fmt --check, and cargo test all pass.
  • MCP-reuse contract grep cargo tree -p fj-client -p fj-core -e normal | grep -E 'clap|crossterm|fluent' returns nothing.
  • fj --format text --json <cmd> exits non-zero with a clear conflict error; --format / --json appear on every subcommand's help (global).
  • fj completion bash output is byte-identical with and without --json.
  • pr status / pr search JSON paths were left untouched (regression-safe), now driven by the global flag.

Note on issue transition

This PR references the issue with a bare #FJ-23 (link only, no State command). On Forgejo the VCS integration applies commit commands at push time, not merge time, so a State Done trailer would resolve FJ-23 prematurely while this PR is still open. State is managed out-of-band: the issue is In Progress now and moves to Done only after a human merges.

🤖 Generated with Claude Code

## Summary Implements FJ-23: a single global output-format flag so every command can emit stable, machine-readable JSON, with unchanged default (human-readable) behavior. - Adds a global `--format <text|json>` flag (clap `global = true`) on `App`, defaulting to `text` and accepted in any position for any subcommand, plus a `--json` shorthand. `--json` is equivalent to `--format json`; combining `--json` with `--format text` is a usage error. - Stores the resolved format once in an `OUTPUT_FORMAT` OnceCell (mirroring `SPECIAL_RENDER`), read everywhere via `crate::output_format()` / `crate::json_output()`. No `run()` signature threads a new parameter. - Under JSON, `--style` is forced to Minimal and all decorative, progress, spinner, interactive-prompt, browser, and fluent-rendered output is suppressed so stdout stays valid JSON. - Every command except `fj completion` emits JSON following the issue taxonomy: reads/lists emit a record or array, mutations emit the affected resource (or `{ "ok": true }`), local side-effecting ops emit `{ "path": ..., "ok": true }`, browse commands emit `{ "url": ... }` without spawning a browser, and `pr diff` / `pr patch` wrap the raw text as `{ "diff": ... }` / `{ "patch": ... }`. - Each command renders through a local `serde::Serialize` view (never serializing `forgejo-api` structs directly), following the `pr_to_json` precedent: RFC 3339 UTC timestamps, stable lowercase English field/enum names that are locale-independent and never routed through fluent. - JSON serialization lives entirely in the `fj` binary, preserving the fj-core / fj-client MCP-reuse contract. - The per-subcommand `json: bool` clap fields on `pr status` / `pr search` are removed and now driven by the global flag; both keep their existing schema unchanged. - `fj completion` is documented as exempt and keeps emitting the raw completion script regardless of `--format`. ## Verification - `cargo build`, `cargo clippy`, `cargo fmt --check`, and `cargo test` all pass. - MCP-reuse contract grep `cargo tree -p fj-client -p fj-core -e normal | grep -E 'clap|crossterm|fluent'` returns nothing. - `fj --format text --json <cmd>` exits non-zero with a clear conflict error; `--format` / `--json` appear on every subcommand's help (global). - `fj completion bash` output is byte-identical with and without `--json`. - `pr status` / `pr search` JSON paths were left untouched (regression-safe), now driven by the global flag. ## Note on issue transition This PR references the issue with a bare `#FJ-23` (link only, no `State` command). On Forgejo the VCS integration applies commit commands at push time, not merge time, so a `State Done` trailer would resolve FJ-23 prematurely while this PR is still open. State is managed out-of-band: the issue is `In Progress` now and moves to `Done` only after a human merges. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(fj): add global --format/--json flag and emit JSON for all commands (FJ-23)
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 36s
Create release / Create release from merged PR (pull_request) Has been skipped
5d2b777cac
Add a global `--format <text|json>` flag (clap `global = true`) to `App`, defaulting to `text`, accepted in any position for any subcommand, plus a `--json` shorthand. `--json` equals `--format json`; pairing `--json` with `--format text` is a usage error. The resolved format is stored once in an `OUTPUT_FORMAT` OnceCell (mirroring `SPECIAL_RENDER`) and read everywhere via `crate::output_format()` / `crate::json_output()`, so no `run()` signature threads a new parameter. Under JSON, `--style` is forced to Minimal and all decorative, progress, spinner, browser, and fluent output is suppressed.

Every command except `fj completion` now emits stable JSON on stdout under `--format json`, following the issue taxonomy: reads/lists emit a record or array, mutations emit the affected resource (or `{ "ok": true }`), local side-effecting ops emit `{ "path": ..., "ok": true }`, browse commands emit `{ "url": ... }` without spawning a browser, and `pr diff` / `pr patch` wrap the raw text as `{ "diff": ... }` / `{ "patch": ... }`.

Each command renders through a local `serde::Serialize` view rather than serializing `forgejo-api` structs directly, following the existing `pr_to_json` precedent: timestamps are RFC 3339 normalized to UTC, field names and enum values are stable lowercase English and locale-independent (never routed through fluent). JSON serialization lives entirely in the `fj` binary, so the fj-core / fj-client MCP-reuse contract is preserved (`cargo tree -p fj-client -p fj-core -e normal | grep -E 'clap|crossterm|fluent'` still returns nothing).

The per-subcommand `json: bool` clap fields on `pr status` and `pr search` are removed and now driven by the global flag; both produce their existing schema unchanged. `fj completion` is documented as exempt and keeps emitting the raw completion script regardless of `--format`.

#FJ-23

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
David merged commit fe3fc354dc into main 2026-05-28 22:30:24 +02:00
David deleted branch feat/global-output-format-fj-23 2026-05-28 22:30:24 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pandoras-box/forgejo-cli!22
No description provided.