From 21c821f5ebb8b0824881f01b156647e8ab26a35f Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Tue, 8 Sep 2026 12:32:32 +0100 Subject: [PATCH 1/6] feat: improve doctor health summary UX --- src/commands/doctor.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index 09a9773..6f74efe 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -12,6 +12,7 @@ import type { DiagnosticCheck, DiagnosticReport, DiagnosticResult, + DiagnosticSummary, } from '../diagnostics/types.js'; import { EXIT_CODES } from '../errors/errors.js'; import { TerminalOutput } from '../output/terminal.js'; @@ -26,6 +27,22 @@ function formatResult(result: DiagnosticResult): string { return `[${result.status.toUpperCase()}] ${result.label}: ${result.message}`; } +function formatOverallHealth(summary: DiagnosticSummary): string { + if (summary.total === 0) { + return 'Overall health: no diagnostics registered.'; + } + + if (summary.fail > 0) { + return 'Overall health: action required.'; + } + + if (summary.warn > 0) { + return 'Overall health: usable with warnings.'; + } + + return 'Overall health: ready.'; +} + function createDefaultDiagnostics(): readonly DiagnosticCheck[] { return [ ...createRuntimeDiagnostics(), @@ -56,6 +73,7 @@ export function renderDiagnosticReport( output.info( `Summary: ${report.summary.pass} passed, ${report.summary.warn} warnings, ${report.summary.fail} failed.`, ); + output.info(formatOverallHealth(report.summary)); } export function createDoctorCommand( From 58dc7a3e63e758eb712dd6df7d90e546e9f543f1 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Tue, 8 Sep 2026 12:32:51 +0100 Subject: [PATCH 2/6] test: cover doctor overall health and warning exit behavior --- tests/doctor.test.ts | 70 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/tests/doctor.test.ts b/tests/doctor.test.ts index 2ac938e..7dec527 100644 --- a/tests/doctor.test.ts +++ b/tests/doctor.test.ts @@ -45,16 +45,25 @@ describe('doctor command', () => { expect(captured.stdoutText()).toContain( 'Summary: 0 passed, 0 warnings, 0 failed.', ); + expect(captured.stdoutText()).toContain( + 'Overall health: no diagnostics registered.', + ); expect(captured.stderrText()).toBe(''); }); - it('aggregates pass, warning, and failure results', () => { + it('aggregates pass, warning, and failure results with actionable health output', () => { const captured = createCapturedTerminalOutput(); let exitCode = -1; const command = createDoctorCommand({ checks: [ check('node', 'Node.js', 'pass', 'Supported version detected.'), - check('docker', 'Docker', 'warn', 'Docker is not available.'), + check( + 'docker', + 'Docker', + 'warn', + 'Docker is not available.', + 'Install Docker only if the selected workflow requires it.', + ), check( 'stellar', 'Stellar CLI', @@ -78,6 +87,9 @@ describe('doctor command', () => { expect(captured.stdoutText()).toContain( '[WARN] Docker: Docker is not available.', ); + expect(captured.stdoutText()).toContain( + 'Remediation: Install Docker only if the selected workflow requires it.', + ); expect(captured.stdoutText()).toContain( '[FAIL] Stellar CLI: Required CLI is missing.', ); @@ -87,6 +99,57 @@ describe('doctor command', () => { expect(captured.stdoutText()).toContain( 'Summary: 1 passed, 1 warnings, 1 failed.', ); + expect(captured.stdoutText()).toContain( + 'Overall health: action required.', + ); + }); + + it('keeps warning-only diagnostics non-fatal', () => { + const captured = createCapturedTerminalOutput(); + let exitCode = -1; + const command = createDoctorCommand({ + checks: [ + check('node', 'Node.js', 'pass', 'Supported version detected.'), + check( + 'docker', + 'Docker', + 'warn', + 'Docker daemon is unavailable.', + 'Start Docker only when a container-based workflow needs it.', + ), + ], + output: captured.output, + setExitCode: (code) => { + exitCode = code; + }, + }); + + command.parse(['node', 'doctor']); + + expect(exitCode).toBe(EXIT_CODES.success); + expect(captured.stdoutText()).toContain( + 'Summary: 1 passed, 1 warnings, 0 failed.', + ); + expect(captured.stdoutText()).toContain( + 'Overall health: usable with warnings.', + ); + }); + + it('reports a fully healthy diagnostic run clearly', () => { + const captured = createCapturedTerminalOutput(); + let exitCode = -1; + const command = createDoctorCommand({ + checks: [check('node', 'Node.js', 'pass', 'Supported version detected.')], + output: captured.output, + setExitCode: (code) => { + exitCode = code; + }, + }); + + command.parse(['node', 'doctor']); + + expect(exitCode).toBe(EXIT_CODES.success); + expect(captured.stdoutText()).toContain('Overall health: ready.'); }); it('converts an unexpected check exception into a safe failed diagnostic', () => { @@ -114,6 +177,9 @@ describe('doctor command', () => { expect(captured.stdoutText()).toContain( '[FAIL] Unsafe check: Diagnostic check could not complete.', ); + expect(captured.stdoutText()).toContain( + 'Remediation: Retry the command or inspect the relevant tool configuration.', + ); expect(captured.stdoutText()).not.toContain('SECRET_TOKEN'); }); }); From e9f6d2c9d937d8c782c1f5a874f8b2419227fc22 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Tue, 8 Sep 2026 12:36:24 +0100 Subject: [PATCH 3/6] style: format doctor UX tests --- tests/doctor.test.ts | 111 ++++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/tests/doctor.test.ts b/tests/doctor.test.ts index 7dec527..fec77fc 100644 --- a/tests/doctor.test.ts +++ b/tests/doctor.test.ts @@ -51,58 +51,61 @@ describe('doctor command', () => { expect(captured.stderrText()).toBe(''); }); - it('aggregates pass, warning, and failure results with actionable health output', () => { - const captured = createCapturedTerminalOutput(); - let exitCode = -1; - const command = createDoctorCommand({ - checks: [ - check('node', 'Node.js', 'pass', 'Supported version detected.'), - check( - 'docker', - 'Docker', - 'warn', - 'Docker is not available.', - 'Install Docker only if the selected workflow requires it.', - ), - check( - 'stellar', - 'Stellar CLI', - 'fail', - 'Required CLI is missing.', - 'Install the official Stellar CLI.', - ), - ], - output: captured.output, - setExitCode: (code) => { - exitCode = code; - }, - }); - - command.parse(['node', 'doctor']); - - expect(exitCode).toBe(EXIT_CODES.diagnostic); - expect(captured.stdoutText()).toContain( - '[PASS] Node.js: Supported version detected.', - ); - expect(captured.stdoutText()).toContain( - '[WARN] Docker: Docker is not available.', - ); - expect(captured.stdoutText()).toContain( - 'Remediation: Install Docker only if the selected workflow requires it.', - ); - expect(captured.stdoutText()).toContain( - '[FAIL] Stellar CLI: Required CLI is missing.', - ); - expect(captured.stdoutText()).toContain( - 'Remediation: Install the official Stellar CLI.', - ); - expect(captured.stdoutText()).toContain( - 'Summary: 1 passed, 1 warnings, 1 failed.', - ); - expect(captured.stdoutText()).toContain( - 'Overall health: action required.', - ); - }); + it( + 'aggregates pass, warning, and failure results with actionable health output', + () => { + const captured = createCapturedTerminalOutput(); + let exitCode = -1; + const command = createDoctorCommand({ + checks: [ + check('node', 'Node.js', 'pass', 'Supported version detected.'), + check( + 'docker', + 'Docker', + 'warn', + 'Docker is not available.', + 'Install Docker only if the selected workflow requires it.', + ), + check( + 'stellar', + 'Stellar CLI', + 'fail', + 'Required CLI is missing.', + 'Install the official Stellar CLI.', + ), + ], + output: captured.output, + setExitCode: (code) => { + exitCode = code; + }, + }); + + command.parse(['node', 'doctor']); + + expect(exitCode).toBe(EXIT_CODES.diagnostic); + expect(captured.stdoutText()).toContain( + '[PASS] Node.js: Supported version detected.', + ); + expect(captured.stdoutText()).toContain( + '[WARN] Docker: Docker is not available.', + ); + expect(captured.stdoutText()).toContain( + 'Remediation: Install Docker only if the selected workflow requires it.', + ); + expect(captured.stdoutText()).toContain( + '[FAIL] Stellar CLI: Required CLI is missing.', + ); + expect(captured.stdoutText()).toContain( + 'Remediation: Install the official Stellar CLI.', + ); + expect(captured.stdoutText()).toContain( + 'Summary: 1 passed, 1 warnings, 1 failed.', + ); + expect(captured.stdoutText()).toContain( + 'Overall health: action required.', + ); + }, + ); it('keeps warning-only diagnostics non-fatal', () => { const captured = createCapturedTerminalOutput(); @@ -139,7 +142,9 @@ describe('doctor command', () => { const captured = createCapturedTerminalOutput(); let exitCode = -1; const command = createDoctorCommand({ - checks: [check('node', 'Node.js', 'pass', 'Supported version detected.')], + checks: [ + check('node', 'Node.js', 'pass', 'Supported version detected.'), + ], output: captured.output, setExitCode: (code) => { exitCode = code; From 476ea2c78b3bf6c40616638a3663bf910fa0c988 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Tue, 8 Sep 2026 12:37:17 +0100 Subject: [PATCH 4/6] chore: diagnose CLI-022 Prettier diff --- .github/workflows/format-cli-022.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/format-cli-022.yml diff --git a/.github/workflows/format-cli-022.yml b/.github/workflows/format-cli-022.yml new file mode 100644 index 0000000..4c2bbea --- /dev/null +++ b/.github/workflows/format-cli-022.yml @@ -0,0 +1,25 @@ +name: Diagnose CLI-022 formatting + +on: + push: + branches: + - feat/cli-022-doctor-summary + +permissions: + contents: read + +jobs: + format-diff: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 + with: + node-version: '22.13.0' + package-manager-cache: false + - run: npm ci --ignore-scripts --no-audit --no-fund + - run: npx --no-install prettier --write tests/doctor.test.ts + - run: git diff -- tests/doctor.test.ts From 6eb329c66895941f1c954099bdec78d9d83639ff Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Tue, 8 Sep 2026 12:39:23 +0100 Subject: [PATCH 5/6] style: apply canonical Prettier layout to doctor tests --- tests/doctor.test.ts | 109 ++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/tests/doctor.test.ts b/tests/doctor.test.ts index fec77fc..381dad6 100644 --- a/tests/doctor.test.ts +++ b/tests/doctor.test.ts @@ -51,61 +51,56 @@ describe('doctor command', () => { expect(captured.stderrText()).toBe(''); }); - it( - 'aggregates pass, warning, and failure results with actionable health output', - () => { - const captured = createCapturedTerminalOutput(); - let exitCode = -1; - const command = createDoctorCommand({ - checks: [ - check('node', 'Node.js', 'pass', 'Supported version detected.'), - check( - 'docker', - 'Docker', - 'warn', - 'Docker is not available.', - 'Install Docker only if the selected workflow requires it.', - ), - check( - 'stellar', - 'Stellar CLI', - 'fail', - 'Required CLI is missing.', - 'Install the official Stellar CLI.', - ), - ], - output: captured.output, - setExitCode: (code) => { - exitCode = code; - }, - }); - - command.parse(['node', 'doctor']); - - expect(exitCode).toBe(EXIT_CODES.diagnostic); - expect(captured.stdoutText()).toContain( - '[PASS] Node.js: Supported version detected.', - ); - expect(captured.stdoutText()).toContain( - '[WARN] Docker: Docker is not available.', - ); - expect(captured.stdoutText()).toContain( - 'Remediation: Install Docker only if the selected workflow requires it.', - ); - expect(captured.stdoutText()).toContain( - '[FAIL] Stellar CLI: Required CLI is missing.', - ); - expect(captured.stdoutText()).toContain( - 'Remediation: Install the official Stellar CLI.', - ); - expect(captured.stdoutText()).toContain( - 'Summary: 1 passed, 1 warnings, 1 failed.', - ); - expect(captured.stdoutText()).toContain( - 'Overall health: action required.', - ); - }, - ); + it('aggregates pass, warning, and failure results with actionable health output', () => { + const captured = createCapturedTerminalOutput(); + let exitCode = -1; + const command = createDoctorCommand({ + checks: [ + check('node', 'Node.js', 'pass', 'Supported version detected.'), + check( + 'docker', + 'Docker', + 'warn', + 'Docker is not available.', + 'Install Docker only if the selected workflow requires it.', + ), + check( + 'stellar', + 'Stellar CLI', + 'fail', + 'Required CLI is missing.', + 'Install the official Stellar CLI.', + ), + ], + output: captured.output, + setExitCode: (code) => { + exitCode = code; + }, + }); + + command.parse(['node', 'doctor']); + + expect(exitCode).toBe(EXIT_CODES.diagnostic); + expect(captured.stdoutText()).toContain( + '[PASS] Node.js: Supported version detected.', + ); + expect(captured.stdoutText()).toContain( + '[WARN] Docker: Docker is not available.', + ); + expect(captured.stdoutText()).toContain( + 'Remediation: Install Docker only if the selected workflow requires it.', + ); + expect(captured.stdoutText()).toContain( + '[FAIL] Stellar CLI: Required CLI is missing.', + ); + expect(captured.stdoutText()).toContain( + 'Remediation: Install the official Stellar CLI.', + ); + expect(captured.stdoutText()).toContain( + 'Summary: 1 passed, 1 warnings, 1 failed.', + ); + expect(captured.stdoutText()).toContain('Overall health: action required.'); + }); it('keeps warning-only diagnostics non-fatal', () => { const captured = createCapturedTerminalOutput(); @@ -142,9 +137,7 @@ describe('doctor command', () => { const captured = createCapturedTerminalOutput(); let exitCode = -1; const command = createDoctorCommand({ - checks: [ - check('node', 'Node.js', 'pass', 'Supported version detected.'), - ], + checks: [check('node', 'Node.js', 'pass', 'Supported version detected.')], output: captured.output, setExitCode: (code) => { exitCode = code; From 56b52f64c877f3e97e19429b0fd0df349d432226 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Tue, 8 Sep 2026 12:39:41 +0100 Subject: [PATCH 6/6] chore: remove temporary CLI-022 formatter diagnostic --- .github/workflows/format-cli-022.yml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .github/workflows/format-cli-022.yml diff --git a/.github/workflows/format-cli-022.yml b/.github/workflows/format-cli-022.yml deleted file mode 100644 index 4c2bbea..0000000 --- a/.github/workflows/format-cli-022.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Diagnose CLI-022 formatting - -on: - push: - branches: - - feat/cli-022-doctor-summary - -permissions: - contents: read - -jobs: - format-diff: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - persist-credentials: false - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 - with: - node-version: '22.13.0' - package-manager-cache: false - - run: npm ci --ignore-scripts --no-audit --no-fund - - run: npx --no-install prettier --write tests/doctor.test.ts - - run: git diff -- tests/doctor.test.ts