Adds smoke tests for the seven read-only commands that share the
run-shaped lifecycle:
- mcp list
- providers list
- models
- agent list
- session list
- stats
- db path
Each test asserts only that the command exits 0 and produces sane
output in the harness's isolated env. They're not behavioral tests —
they're the cheapest possible signal that the dependency-layer wiring
(config load, DB init, server boot, provider resolution) doesn't crash
for the broad class of "no inputs, no side effects" commands. A
regression in any shared layer will fail one or more of these tests.
Pushes behavioral coverage of the CLI surface from ~14% to ~50%.
Findings while wiring assertions:
- \`providers list\` reflects credentials + env vars, not
config-injected static providers. The assertion verifies the
section headers ("Credentials" / "Environment") to lock in the
output shape.
- \`db path\` returns SQLite's \`:memory:\` under harness isolation
(no on-disk pollution between tests). The assertion accepts either
\`:memory:\` or a real path.
7/7 pass in ~7s; full CLI suite stays green at 331/331.
yargs wraps the \`[string] [default: "..."]\` clause based on the
pre-normalized default value's character length, so a different random
tmpdir width produces a different leading-whitespace count on the
wrapped continuation line. After normalizing the path to \`<HOME>\` we
were left with a one-space drift between runs.
Collapse the wrap-dependent whitespace immediately before the clause
so the snapshot is byte-stable regardless of home path length.
Verified by deleting the .snap and regenerating across 3 runs.
Applied review findings from the simplify pass:
1. Extract fromBunStream(name, get) and forkStderrDrain(stream, into)
helpers — 4 duplicated Stream.fromReadableStream call sites collapse
to one factory, and the identical stderr drain across serve/acp is
now a single helper. Error messages now include the underlying cause
instead of swallowing it.
2. acp.send awaits proc.stdin.write's backpressure promise. The bare
Effect.sync was discarding the Promise<number> form, which can
reorder ndjson lines under pipe-buffer-full conditions and corrupt
framing.
3. acp.close drops the try/catch around proc.stdin.end() — idempotent
in Bun, the bare catch only masked future regressions.
4. Effect.ignore on stream drains is now Effect.ignore({ log: true })
so a real protocol or decode error surfaces in test debug output
instead of disappearing silently.
5. Help-snapshots replaces the manual failures[] accumulator + continue
with Effect.partition. Same behavior, no mutable state, declarative.
324/324 CLI tests stay green; typecheck clean.
One test file. Spawns `opencode <cmd> --help` for every documented
command + key subcommand (35 in total) in parallel under concurrency:8,
snapshots the stderr output (yargs writes --help to stderr, not stdout).
Snapshots are normalized — the tmpdir prefix that bleeds through
`acp --cwd`'s default is rewritten to `<HOME>` so test runs in
different sandboxes stay stable. macOS `/private` realpath form and
the unresolved `os.tmpdir()` form both covered.
Pinned snapshots catch flag removals, renames, reordering, and exit-code
regressions across the entire user-facing CLI surface in one place.
Diff in the .snap file is the surface-change report.
Excluded: `opencode completion --help` is a yargs built-in that emits
top-level help and exits 1; not a real opencode command.
~6s wall-clock thanks to parallel spawns.
Adds the second long-lived-command builder to the cli-process harness:
`opencode.acp(opts)` spawns the real CLI in JSON-RPC-over-stdio mode
and returns a duplex handle (`send`/`receive`/`close`/`exited`) scoped
to the test's lifetime. Stdin EOF triggers a clean shutdown; the scope
finalizer also falls back to SIGTERM after 2s for a hung child.
ACP frames each JSON-RPC message as one ndjson line on stdout. The
builder forks a scope-bound `Stream.fromReadableStream` + `splitLines`
pipeline that feeds parsed responses into a `Queue.unbounded`, so tests
can `yield* acp.receive` without worrying about backpressure or framing.
Two smoke tests:
- `initialize` round-trip — sends the protocol handshake from the ACP
README and asserts the response advertises the same protocolVersion
and a non-empty agentCapabilities block.
- Clean shutdown on stdin EOF — proves the scope finalizer's stdin.end()
triggers a graceful exit, not a SIGTERM-fallback exit.
Reduce avoidable setup costs in slow opencode tests while preserving reviewed coverage and recording the benchmark evidence for follow-up test-suite work.
String.length counts code points, not display columns, so CJK
characters and emoji that occupy two terminal cells caused
misaligned cursors, broken mention triggers, and incorrect
history restoration offsets.
Use Bun.stringWidth for now, we need an alternative for this.
Fix#26716Close#26922