From 9405959b2bd974b1f4f02f19202f9f331232a0de Mon Sep 17 00:00:00 2001 From: William Weishuhn Date: Mon, 23 Mar 2026 13:51:55 -0700 Subject: [PATCH] =?UTF-8?q?improve:=20usage-driven=20UX=20=E2=80=94=20prom?= =?UTF-8?q?ote=20security,=20simplify=20help,=20test=20as=20hero?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Data-driven changes based on 1,792 telemetry events from 745 sessions: - Security findings now shown by default in terminal output (was 2% opt-in, lightweight security already runs but results were hidden) - `test` promoted to first/recommended command (11% of usage, growing) - Interactive menu reorganized: test first, lock/history added to CI group - Hide dead commands from help: record (3 uses), replay (2), suggest (5) — still work, just hidden from --help to reduce noise - Help text simplified to 7 essential commands Co-Authored-By: Claude Opus 4.6 (1M context) --- src/cli.ts | 38 ++++++++++++++++++++--------------- src/commands/record-replay.ts | 4 ++-- src/reporters/terminal.ts | 16 +++++++++++++++ 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index f020168..1838adb 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -41,19 +41,26 @@ const MENU_GROUPS: MenuGroup[] = [ { heading: "", items: [ - { command: ["scan"], label: "scan", outcome: "Check all your MCP servers", recommended: true }, - { command: ["scan", "deep"], label: "scan deep", outcome: "^ plus invoke tools to verify they actually work" }, - { command: ["suggest"], label: "suggest", outcome: "Discover MCP servers for your stack" }, - { command: ["score"], label: "score", outcome: "Health score (0-100) for a specific server" }, + { command: ["test"], label: "test ", outcome: "Test a specific MCP server", recommended: true }, + { command: ["scan"], label: "scan", outcome: "Check all your configured MCP servers" }, + { command: ["scan", "deep"], label: "scan deep", outcome: "^ plus invoke tools to verify they work" }, ], }, { heading: "CI / Regression Testing", items: [ - { command: ["watch"], label: "watch", outcome: "Run a check, diff against previous, alert on regressions" }, - { command: ["record"], label: "record", outcome: "Capture a session for offline replay or CI" }, - { command: ["diff"], label: "diff", outcome: "Compare two runs for regressions" }, - { command: ["test"], label: "test", outcome: "Test a single server by command" }, + { command: ["watch"], label: "watch", outcome: "Run check, diff against previous, alert regressions" }, + { command: ["lock"], label: "lock", outcome: "Snapshot MCP server schemas into a lock file" }, + { command: ["lock", "verify"], label: "lock verify", outcome: "Verify servers match the lock file" }, + { command: ["diff"], label: "diff", outcome: "Compare two run artifacts for regressions" }, + { command: ["history"], label: "history", outcome: "Show health score trends over time" }, + ], + }, + { + heading: "Scoring & Badges", + items: [ + { command: ["score"], label: "score", outcome: "Health score (0-100) for a specific server" }, + { command: ["badge"], label: "badge", outcome: "Generate an SVG health badge for README" }, ], }, ]; @@ -219,17 +226,16 @@ async function main(): Promise { "", ` ${c(ANSI.bold, "Quick Start")}`, "", - ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} scan`)} Check all your MCP servers`, - ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} scan deep`)} ^ plus invoke tools to verify they work`, - ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} suggest`)} Discover MCP servers for your stack`, - ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} score`)} ${c(ANSI.dim, "")} Health score (0-100) for any server`, + ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} test`)} ${c(ANSI.dim, "")} Test a specific MCP server`, + ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} scan`)} Check all your configured servers`, + ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} scan deep`)} ^ plus invoke tools to verify they work`, "", ` ${c(ANSI.bold, "CI / Regression Testing")}`, "", - ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} watch`)} ${c(ANSI.dim, "")} Run check, diff against previous, alert regressions`, - ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} record`)} ${c(ANSI.dim, "")} Capture a session for offline replay`, - ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} diff`)} ${c(ANSI.dim, " ")} Compare two runs for regressions`, - ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} badge`)} ${c(ANSI.dim, "")} Generate a health badge for README`, + ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} watch`)} ${c(ANSI.dim, "")} Diff against previous run, alert regressions`, + ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} lock`)} Snapshot server schemas (like package-lock)`, + ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} lock verify`)} Verify no schema drift since last lock`, + ` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} diff`)} ${c(ANSI.dim, " ")} Compare two runs for regressions`, "", ` ${c(ANSI.dim, `Run ${bin} --help for details on any command.`)}`, "", diff --git a/src/commands/record-replay.ts b/src/commands/record-replay.ts index 06678a9..8b75b10 100644 --- a/src/commands/record-replay.ts +++ b/src/commands/record-replay.ts @@ -25,7 +25,7 @@ export function registerRecordReplayCommands(program: Command, bin: string): voi // ── record ───────────────────────────────────────────────────────────── program - .command("record") + .command("record", { hidden: true }) .passThroughOptions() .description("Record a server session to a cassette file for replay.") .argument("[command...]", "Server command and arguments to run.") @@ -71,7 +71,7 @@ export function registerRecordReplayCommands(program: Command, bin: string): voi // ── replay ───────────────────────────────────────────────────────────── program - .command("replay") + .command("replay", { hidden: true }) .description("Replay a cassette file offline — no live server needed.") .argument("", "Path to a cassette JSON file.") .option("--no-color", "Disable colored output.") diff --git a/src/reporters/terminal.ts b/src/reporters/terminal.ts index 27caf1d..db4e238 100644 --- a/src/reporters/terminal.ts +++ b/src/reporters/terminal.ts @@ -199,6 +199,22 @@ function renderRunTerminal(artifact: RunArtifact): string { lines.push(formatCheck(check)); } + // Show security-lite findings prominently even without --security flag + const secLite = artifact.checks.find(ch => ch.id === "security-lite"); + if (secLite && secLite.status !== "pass" && secLite.evidence.length > 0) { + const diagnostics = secLite.evidence[0]?.diagnostics ?? []; + if (diagnostics.length > 0) { + lines.push(""); + lines.push(co(ANSI.red, " Security:")); + for (const d of diagnostics.slice(0, 3)) { + lines.push(` ${co(ANSI.dim, "→")} ${d}`); + } + if (diagnostics.length > 3) { + lines.push(` ${co(ANSI.dim, ` ...and ${diagnostics.length - 3} more (run with --security for full scan)`)}`); + } + } + } + return lines.join("\n"); }