Problem
Many mship commands already detect TTY and switch output shape: table-ish text for humans, JSON for pipes. This works for interactive use. It is unreliable for:
- CI:
mship invoked from a GitHub Actions step where stdout is a TTY in some runner configs and not in others → inconsistent output shape across runs.
- Agent loops: a Claude / Codex / Aider subprocess that allocates a pty to capture streaming output gets the TTY-branch output when it wanted JSON, or vice-versa.
- Log capture: users piping through
tee want the JSON shape but also want to watch progress — TTY detection forces a choice.
- Color bleed: ANSI escape codes leak into log collectors, PR comment bodies, and clipboard paste when TTY detection gets it wrong.
Implicit-behavior-based-on-ambient-environment is the classic source of "works on my machine / fails in CI" friction, and it's exactly the kind of non-determinism a substrate should not force on its callers.
Proposal
Add three flags, consistently available across every output-producing mship subcommand:
--json — force JSON output regardless of TTY state. Implies --no-color. For commands that today emit human text with no JSON path, this means defining and documenting one.
--quiet / -q — suppress non-essential stderr (progress lines, advisory warnings, informational status). Errors and exit codes unchanged.
--no-color — strip ANSI color codes from all output streams. Equivalent to (and overridden by) NO_COLOR=1 env var per https://no-color.org/.
Ordering / precedence:
- CLI flag wins over env var wins over TTY auto-detection.
MSHIP_JSON=1, MSHIP_QUIET=1, NO_COLOR=1 env vars for shell-profile defaults.
--json and --no-color are orthogonal (--json already implies --no-color, but --no-color alone doesn't force JSON).
Commands in scope (non-exhaustive — anywhere output is produced):
mship status, mship list, mship pr, mship context, mship dispatch
mship spawn, mship finish, mship close, mship commit, mship journal, mship bind refresh
mship debug hypothesis/rule-out/resolved, mship reconcile
mship test (json exit payload + human progress are already split; this just makes it explicit)
Why this is polish, not core leverage — but still worth doing
The substrate thesis is that mship owns the hand-off boundary. Hand-offs into CI and into agent subprocesses are two of the three main boundaries (the third being human). Making the output shape deterministic at those boundaries is a correctness property, not a UX nicety — a flaky output format turns every "if stdout contains X then Y" downstream rule into a source of CI drift.
It's small because the machinery already exists; this issue is a consistency pass to make sure every command exposes the same flag surface and respects the same precedence rules.
Scope cuts
- No new output shapes.
--json emits whatever JSON the command already emits in TTY-off mode. If a command has no JSON path today, define a minimal one (slug + result + error, basically), don't design a rich schema.
- No per-field selectors (
--json --fields=slug,phase). Downstream tooling uses jq; this is not a reinvention of jq.
- No color themes.
--no-color is boolean off, not a theme system.
- No structured stderr.
--quiet suppresses; it doesn't add a stderr JSON channel. If you want structured diagnostics, that's a separate issue.
Acceptance
- Every
mship subcommand accepts --json, --quiet, --no-color (or documents that the flag is a no-op for that command, e.g. --json on a command with no output).
- Precedence is documented in
mship --help and in the skill doc.
- Integration test:
mship <cmd> --json run under a TTY (via pty) produces the same bytes as run without a TTY.
- CI smoke: one Actions job runs a representative command and asserts on exact output bytes with the flags set.
Problem
Many
mshipcommands already detect TTY and switch output shape: table-ish text for humans, JSON for pipes. This works for interactive use. It is unreliable for:mshipinvoked from a GitHub Actions step where stdout is a TTY in some runner configs and not in others → inconsistent output shape across runs.teewant the JSON shape but also want to watch progress — TTY detection forces a choice.Implicit-behavior-based-on-ambient-environment is the classic source of "works on my machine / fails in CI" friction, and it's exactly the kind of non-determinism a substrate should not force on its callers.
Proposal
Add three flags, consistently available across every output-producing
mshipsubcommand:--json— force JSON output regardless of TTY state. Implies--no-color. For commands that today emit human text with no JSON path, this means defining and documenting one.--quiet/-q— suppress non-essential stderr (progress lines, advisory warnings, informational status). Errors and exit codes unchanged.--no-color— strip ANSI color codes from all output streams. Equivalent to (and overridden by)NO_COLOR=1env var per https://no-color.org/.Ordering / precedence:
MSHIP_JSON=1,MSHIP_QUIET=1,NO_COLOR=1env vars for shell-profile defaults.--jsonand--no-colorare orthogonal (--jsonalready implies--no-color, but--no-coloralone doesn't force JSON).Commands in scope (non-exhaustive — anywhere output is produced):
mship status,mship list,mship pr,mship context,mship dispatchmship spawn,mship finish,mship close,mship commit,mship journal,mship bind refreshmship debug hypothesis/rule-out/resolved,mship reconcilemship test(json exit payload + human progress are already split; this just makes it explicit)Why this is polish, not core leverage — but still worth doing
The substrate thesis is that mship owns the hand-off boundary. Hand-offs into CI and into agent subprocesses are two of the three main boundaries (the third being human). Making the output shape deterministic at those boundaries is a correctness property, not a UX nicety — a flaky output format turns every "if stdout contains X then Y" downstream rule into a source of CI drift.
It's small because the machinery already exists; this issue is a consistency pass to make sure every command exposes the same flag surface and respects the same precedence rules.
Scope cuts
--jsonemits whatever JSON the command already emits in TTY-off mode. If a command has no JSON path today, define a minimal one (slug + result + error, basically), don't design a rich schema.--json --fields=slug,phase). Downstream tooling usesjq; this is not a reinvention of jq.--no-coloris boolean off, not a theme system.--quietsuppresses; it doesn't add a stderr JSON channel. If you want structured diagnostics, that's a separate issue.Acceptance
mshipsubcommand accepts--json,--quiet,--no-color(or documents that the flag is a no-op for that command, e.g.--jsonon a command with no output).mship --helpand in the skill doc.mship <cmd> --jsonrun under a TTY (via pty) produces the same bytes as run without a TTY.