Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions specs/reporters.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ After all skills complete, findings are rendered to stdout. This is separate fro
│ │
│ User input is interpolated directly into a SQL query... │
│ │
│ [high confidence] │
│ │
│ Suggested fix: │
│ - const query = buildQuery(userId); │
│ + const query = buildQuery(sanitize(userId)); │
Expand All @@ -268,10 +270,12 @@ After all skills complete, findings are rendered to stdout. This is separate fro
│ │
│ External API response is cast without validation. │
│ │
│ [medium confidence] │
│ │
└───────────────────────────────────────────────────────────┘
```

Each finding shows: severity badge, title, location with elapsed time, source code line (read from disk), description, and suggested fix diff (colored: green for `+`, red for `-`, cyan for `@@`).
Each finding shows: severity badge, title, location with elapsed time, source code line (read from disk), description, confidence badge (when present), and suggested fix diff (colored: green for `+`, red for `-`, cyan for `@@`). The confidence badge appears after the description, separated by a blank line. It is colored by level: green for high, yellow for medium, red for low. When confidence is not provided, the badge and its blank line are omitted.

When fixable findings exist and interactive mode is active (TTY, no `--fix`, no `--json`, not quiet, not interrupted), suggested fix diffs are **suppressed** from the report because they will be shown in the interactive fix step-through (see Section below). All other finding fields (severity, title, location, description) still render normally.

Expand Down Expand Up @@ -453,14 +457,16 @@ Findings are displayed in reading order (file ascending, line ascending). Each f
User input is interpolated directly into a SQL query...
Use parameterized queries instead

[high confidence]

@@ -42,1 +42,1 @@
- const query = buildQuery(userId);
+ const query = buildQuery(sanitize(userId));

[y]es / [n]o / [a]pply all / [s]kip all
```

Severity badge, counter, title, location, description, fix description (if present), and colored diff.
Severity badge, counter, title, location, description, fix description (if present), confidence badge (when present), and colored diff.

**Prompt keys** (single keypress, no Enter):

Expand Down Expand Up @@ -608,6 +614,7 @@ All reporters use shared formatters from `src/cli/output/formatters.ts`.
| `formatFindingCountsPlain(counts)` | `Record<Severity, number>` | Plain text | `2 findings (1 high, 1 medium)` |
| `formatSeverityBadge(severity)` | `Severity` | Colored dot + text | `. (high)` |
| `formatSeverityPlain(severity)` | `Severity` | Bracketed | `[high]` |
| `formatConfidenceBadge(confidence?)` | `Confidence \| undefined` | Colored bracketed (empty if undefined) | `[high confidence]` |
| `countBySeverity(findings)` | `Finding[]` | `Record<Severity, number>` | `{ critical: 0, high: 1, ... }` |
| `pluralize(count, singular, plural?)` | `number, string` | Pluralized word | `file` / `files` |

Expand Down
8 changes: 7 additions & 1 deletion src/cli/fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import chalk from 'chalk';
import figures from 'figures';
import type { Finding, SkillReport } from '../types/index.js';
import { formatSeverityBadge, pluralize, type Reporter } from './output/index.js';
import { formatSeverityBadge, formatConfidenceBadge, pluralize, type Reporter } from './output/index.js';
import { ICON_CHECK } from './output/icons.js';
import { Verbosity } from './output/verbosity.js';
import { applyUnifiedDiff } from './diff-apply.js';
Expand Down Expand Up @@ -192,6 +192,12 @@ export async function runInteractiveFixFlow(
console.error(` ${suggestedFix.description}`);
}

// Confidence
if (finding.confidence) {
console.error('');
console.error(` ${formatConfidenceBadge(finding.confidence)}`);
}

console.error('');

// Display the diff
Expand Down
14 changes: 14 additions & 0 deletions src/cli/output/formatters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
padRight,
formatStatsCompact,
formatSeverityBadge,
formatConfidenceBadge,
} from './formatters.js';
import type { Severity, UsageStats, AuxiliaryUsageMap } from '../../types/index.js';

Expand Down Expand Up @@ -92,6 +93,19 @@ describe('formatSeverityBadge', () => {
});
});

describe('formatConfidenceBadge', () => {
it('includes confidence text for each level', () => {
expect(formatConfidenceBadge('high')).toContain('high confidence');
expect(formatConfidenceBadge('medium')).toContain('medium confidence');
expect(formatConfidenceBadge('low')).toContain('low confidence');
});

it('returns empty string for undefined confidence', () => {
expect(formatConfidenceBadge(undefined)).toBe('');
});
});


describe('formatProgress', () => {
it('formats progress indicator', () => {
// Note: formatProgress uses chalk.dim, so we just check it contains the numbers
Expand Down
22 changes: 21 additions & 1 deletion src/cli/output/formatters.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import chalk from 'chalk';
import figures from 'figures';
import type { Severity, Finding, FileChange, UsageStats, AuxiliaryUsageMap } from '../../types/index.js';
import type { Severity, Confidence, Finding, FileChange, UsageStats, AuxiliaryUsageMap } from '../../types/index.js';

/**
* Capitalize the first letter of a string.
Expand Down Expand Up @@ -76,6 +76,26 @@ export function formatSeverityPlain(severity: Severity): string {
return `[${severity}]`;
}

/**
* Confidence configuration for display.
*/
const CONFIDENCE_CONFIG: Record<Confidence, { color: typeof chalk.dim }> = {
high: { color: chalk.green },
medium: { color: chalk.yellow },
low: { color: chalk.red },
};

/**
* Format a confidence badge for terminal output.
* Returns empty string if confidence is undefined.
*/
export function formatConfidenceBadge(confidence: Confidence | undefined): string {
if (!confidence) return '';
const config = CONFIDENCE_CONFIG[confidence];
return config.color(`[${confidence} confidence]`);
}


/**
* Format a file location string.
*/
Expand Down
1 change: 1 addition & 0 deletions src/cli/output/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
formatSeverityBadge,
formatSeverityDot,
formatSeverityPlain,
formatConfidenceBadge,
formatFindingCounts,
formatFindingCountsPlain,
formatProgress,
Expand Down
43 changes: 43 additions & 0 deletions src/cli/terminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,49 @@ describe('renderTerminalReport', () => {
expect(output).toContain('Test Finding');
expect(output).toContain('This is a test finding');
});

it('renders confidence badge after description in TTY mode', () => {
const report = createReport({
findings: [
createFinding({
confidence: 'high',
title: 'Finding with confidence',
description: 'This is the description',
}),
],
});

const output = renderTerminalReport([report], {
isTTY: true,
supportsColor: false,
columns: 80,
});

expect(output).toContain('high confidence');
expect(output).toContain('Finding with confidence');
// Confidence should appear after the description, not on the title line
const lines = output.split('\n');
const titleLine = lines.findIndex((l) => l.includes('Finding with confidence'));
const confidenceLine = lines.findIndex((l) => l.includes('high confidence'));
const descriptionLine = lines.findIndex((l) => l.includes('This is the description'));
expect(confidenceLine).toBeGreaterThan(descriptionLine);
expect(confidenceLine).toBeGreaterThan(titleLine);
});

it('does not show confidence badge when not present', () => {
const report = createReport({
findings: [createFinding({ title: 'Finding without trust level' })],
});

const output = renderTerminalReport([report], {
isTTY: true,
supportsColor: false,
columns: 80,
});

expect(output).toContain('Finding without trust level');
expect(output).not.toContain('confidence');
});
});

describe('CI (non-TTY) rendering', () => {
Expand Down
12 changes: 9 additions & 3 deletions src/cli/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { filterFindings } from '../types/index.js';
import {
formatSeverityBadge,
formatSeverityPlain,
formatConfidenceBadge,
formatFindingCounts,
formatFindingCountsPlain,
formatDuration,
Expand Down Expand Up @@ -61,9 +62,8 @@ function formatFindingTTY(finding: Finding, options?: RenderOptions): string[] {
const badge = formatSeverityBadge(finding.severity);
const color = SEVERITY_COLORS[finding.severity];

// Title line with severity dot
const titleParts = [badge, color(finding.title)];
lines.push(titleParts.join(' '));
// Title line with severity badge
lines.push(`${badge} ${color(finding.title)}`);

// Location with elapsed time
if (finding.location) {
Expand Down Expand Up @@ -105,6 +105,12 @@ function formatFindingTTY(finding: Finding, options?: RenderOptions): string[] {
lines.push(` ${chalk.dim.italic(finding.verification)}`);
}

// Confidence level
if (finding.confidence) {
lines.push('');
lines.push(` ${formatConfidenceBadge(finding.confidence)}`);
}

// Suggested fix diff if available (suppress when step-through will show it)
if (finding.suggestedFix?.diff && !options?.suppressFixDiffs) {
lines.push('');
Expand Down