* fix(telemetry): suppress first-run notice in --json mode The first-run telemetry disclosure notice was written to stdout from the global preAction hook. On a user's first-ever command with --json this polluted stdout and could break JSON parsers. Read the executing command's --json flag (actionCommand.opts().json) and, when set, skip the notice and leave noticeSeen unset so the disclosure is deferred to the first later non-JSON run rather than lost. Spinner suppression, new-change --json output, and structured JSON errors already landed on main (#960, #1190); this closes the one remaining stdout writer in --json mode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * harden: detect --json from argv to cover all invocation forms The preAction guard read actionCommand.opts().json, which only sees a declared leaf option. That missed two supported --json forms that emit a single JSON document to stdout: - openspec store --json (permissive group reads --json from residual args; never declares the option, so opts().json is undefined) - openspec workset --json <sub> (--json on the parent group, consumed before the leaf; leaf opts().json is undefined) Both would still print the first-run telemetry notice ahead of their JSON. Detect --json from process.argv instead: it covers leaf, parent, and residual-arg forms uniformly. Suppressing is always safe (the disclosure defers to the next non-JSON run, never lost), so a broad argv check is the correct, conservative signal. Also add a direct assertion that noticeSeen stays unset after a silent run, and note the pre-existing raw-stdout commands (completion generate, config get/path, __complete) as out of scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor: derive --json from parsed command state + regression test Replace the process.argv check with isJsonRun(command), an exported pure helper that reads Commander's parsed state: optsWithGlobals().json (leaf and parent-group forms) OR command.args (residual --json on permissive bare groups like store). This is tied to the actually-parsed command rather than raw args, and — unlike process.argv — is unit-testable in-process. Add test/core/cli-is-json-run.test.ts: a synthetic program reproducing all three registration patterns proves isJsonRun returns true for status --json, store --json, workset --json list, and workset list --json, and false otherwise. This locks in the store/workset coverage against future regressions (an e2e test can't: telemetry is disabled under CI, so the notice never fires there). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(spec): qualify first-command notice scenario as non-JSON The generic 'First command execution' scenario asserted the notice displays on every first command, contradicting the JSON scenario that says it does not. Qualify it as 'without --json' so the required behavior is unambiguous. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2.1 KiB
Suppress the first-run telemetry notice in --json mode
Why
openspec <cmd> --json is meant to emit exactly one machine-readable JSON
document on stdout so agents and automation can parse it. Spinner suppression
and structured JSON errors already ship on main, but one stdout writer remains:
the first-run telemetry disclosure notice.
On a user's first-ever command, maybeShowTelemetryNotice() runs from the
global preAction hook and console.logs the disclosure to stdout — before
the command's JSON payload. A --json consumer parsing that first run gets
invalid JSON. It is first-run-only (the notice sets noticeSeen), but that is
exactly the run an automation is most likely to hit on a fresh machine or CI
image.
What Changes
maybeShowTelemetryNotice()accepts asilentoption. When silent, it prints nothing and leavesnoticeSeenunset, so the disclosure is deferred rather than skipped.- The
preActionhook passessilent: truewhen the executing command asked for JSON, decided byisJsonRun(command).--jsonreaches commands three ways, so a single parsed option (opts().json) is not enough: on the leaf (status --json), on a parent group read viaoptsWithGlobals(workset --json list), and as a residual arg on permissive groups that never declare the option (openspec store --json).isJsonRunchecksoptsWithGlobals().jsonandcommand.args, covering all three.
Net effect: any --json invocation never emits the notice on stdout; the user
still sees the disclosure on their first later non-JSON run. Suppressing is
always safe — worst case the disclosure defers one run. Telemetry remains opt-out
and otherwise unchanged.
Out of scope: a few commands write scriptable output to stdout without a --json
flag (completion generate, config get, config path, the hidden __complete).
Their first-run notice pollution is a separate, pre-existing issue not addressed
here.
Impact
- Affected specs:
telemetry(MODIFIED: First-run telemetry notice) - Affected code:
src/telemetry/index.ts,src/cli/index.ts - No change to non-JSON behavior; no new events or data collected.