Diagnostic and Report Formatting Design

This document specifies the diagnostic and report formatting system implementing REQ-007’s sophisticated error reporting requirements. The design provides multi-format output capabilities, precise positioning, and context display.

Design Philosophy

The design focuses on the essential components needed for sophisticated error reporting:

Architecture Goal: Provide excellent developer experience with clear, contextual error reporting Integration Focus: Seamless compatibility with the validated linter core framework

Essential Components

The diagnostic system consists of four core components:

Enhanced Diagnostic Structure

Embeds existing Violation objects with additional metadata for sophisticated presentation.

Context Display System

Reuses validated context extraction from POC with enhanced presentation options.

Unified Report Structure

Single report type handling both individual files and aggregate analysis results.

Pluggable Renderer System

Simple protocol-based renderers for text, JSON, and structured output formats.

Enhanced Diagnostic Design

Diagnostic Structure

The diagnostic system extends existing violations with essential metadata:

from . import __

class DiagnosticSeverity( __.enum.Enum ):
    ''' Diagnostic severity levels with deterministic ordering. '''
    Info = 'info'
    Warning = 'warning'
    Error = 'error'

    @property
    def sort_order( self ) -> int:
        ''' Returns numeric value for deterministic sorting. '''

class Diagnostic( __.immut.DataclassObject ):
    ''' Enhanced diagnostic embedding existing violation with presentation metadata.

        Diagnostics extend the validated Violation structure with additional
        information needed for sophisticated error reporting while maintaining
        compatibility with the existing linter core framework.
    '''
    violation: __.typx.Annotated[
        __.violations.Violation, __.ddoc.Doc( 'Core violation from linter framework.' ) ]
    rule_name: __.typx.Annotated[
        str, __.ddoc.Doc( 'Human-readable rule name for display.' ) ]
    category: __.typx.Annotated[
        str, __.ddoc.Doc( 'Rule category (readability, discoverability, robustness).' ) ]
    severity: DiagnosticSeverity
    auto_fixable: __.typx.Annotated[
        bool, __.ddoc.Doc( 'Whether diagnostic supports automatic fixing.' ) ] = False
    documentation_url: __.typx.Annotated[
        __.Absential[ str ], __.ddoc.Doc( 'URL to rule documentation.' ) ] = __.absent

    @property
    def location_sort_key( self ) -> tuple[ str, int, int ]:
        ''' Returns sortable key for deterministic diagnostic ordering. '''

    @property
    def filename( self ) -> str:
        ''' Returns filename from embedded violation for convenience. '''

    @property
    def line( self ) -> int:
        ''' Returns line number from embedded violation for convenience. '''

    @property
    def column( self ) -> int:
        ''' Returns column from embedded violation for convenience. '''

    @property
    def message( self ) -> str:
        ''' Returns message from embedded violation for convenience. '''

Design Rationale: Violation vs Diagnostic Distinction

The diagnostic system embeds existing Violation objects rather than replacing them to maintain clear separation of concerns:

Violation (Core Rule Execution Data)

Contains essential detection information generated during rule analysis:

  • Location data (filename, line, column)

  • Core message describing the violation

  • Rule identifier (VBL code)

  • Basic severity level

Violations focus on what went wrong and where it occurred without carrying presentation overhead.

Diagnostic (Enhanced Presentation Layer)

Adds metadata for sophisticated error reporting by consulting the rule registry:

  • Human-readable rule names for display

  • Category classification for organization

  • Auto-fixable capability flags

  • Documentation URLs for guidance

  • Enhanced severity enum with deterministic ordering

Diagnostics focus on how to present violations and what can be done about them.

This separation allows:

  • Rules to remain focused on detection logic without presentation concerns

  • Presentation metadata to be managed centrally in the rule registry

  • Multiple presentation layers to enhance the same violations differently

  • Core violation data to remain lightweight and performance-optimized

The embedded approach avoids duplication while providing a clear upgrade path from basic rule output to sophisticated error reporting.

Context Display System

Enhanced Context Presentation

The context system reuses the validated POC pattern with enhanced presentation:

from . import __

class ContextStyle( __.enum.Enum ):
    ''' Context presentation styles for different output needs. '''
    Plain = 'plain'       # No formatting, just line numbers
    Highlighted = 'highlighted'  # Violation line marked with >
    Compact = 'compact'   # Minimal context for space-constrained output

class SourceContext( __.immut.DataclassObject ):
    ''' Source code context with presentation formatting from validated POC patterns.

        Extends the proven context extraction from blank_line_rule_poc.py
        with configurable presentation styles for different output formats.
    '''
    filename: str
    violation_line_number: __.typx.Annotated[
        int, __.ddoc.Doc( 'One-indexed line containing violation.' ) ]
    context_lines: __.typx.Annotated[
        tuple[ str, ... ], __.ddoc.Doc( 'Source lines including violation and surrounding context.' ) ]
    context_line_number: __.typx.Annotated[
        int, __.ddoc.Doc( 'One-indexed line number of first context line.' ) ]
    style: ContextStyle

    def format_for_display( self ) -> tuple[ str, ... ]:
        ''' Returns formatted context lines with line numbers and violation markers. '''

    def get_line_number_width( self ) -> int:
        ''' Returns character width needed for line number formatting. '''

class ContextExtractor:
    ''' Context extraction serving both rule framework and diagnostic reporting with configurable presentation. '''

    def __init__(
        self,
        source_lines: __.typx.Annotated[
            tuple[ str, ... ], __.ddoc.Doc( 'Source file lines for extraction.' ) ],
        style_default: ContextStyle = ContextStyle.Highlighted,
    ) -> None: ...

    def extract_context(
        self,
        filename: str,
        line_number: int,
        context_size: __.typx.Annotated[
            int, __.ddoc.Doc( 'Lines to show before and after violation.' ) ] = 2,
    ) -> __.typx.Annotated[
        SourceContext,
        __.ddoc.Doc( 'Source context for specified line.' ),
    ]: ...

    def extract_multiple_contexts(
        self,
        locations: __.cabc.Sequence[ tuple[ str, int ] ],
        context_size: int = 2,
    ) -> __.typx.Annotated[
        tuple[ SourceContext, ... ],
        __.ddoc.Doc( 'Optimized context extraction for multiple locations.' ),
    ]: ...

Unified Report Structure

Single Report Type

The report system uses one structure for both single files and aggregates:

from . import __

class Report( __.immut.DataclassObject ):
    ''' Unified diagnostic report supporting both single files and aggregates.

        Simplifies the reporting architecture by using one structure that
        scales from single file analysis to multi-file aggregate results.
    '''
    diagnostics: __.typx.Annotated[
        tuple[ Diagnostic, ... ], __.ddoc.Doc( 'All diagnostics sorted deterministically.' ) ]
    contexts: __.typx.Annotated[
        tuple[ SourceContext, ... ],
        __.ddoc.Doc( 'Source contexts when context extraction enabled.' ) ]
    files: __.typx.Annotated[
        tuple[ str, ... ], __.ddoc.Doc( 'Files analyzed in this report.' ) ]
    metadata: __.typx.Annotated[
        __.immut.Dictionary[ str, __.typx.Any ],
        __.ddoc.Doc( 'Analysis metadata including timing and configuration.' ) ]

    def diagnostic_count( self ) -> int:
        ''' Returns total number of diagnostics in report. '''

    def error_count( self ) -> int:
        ''' Returns number of error-severity diagnostics. '''

    def warning_count( self ) -> int:
        ''' Returns number of warning-severity diagnostics. '''

    def files_with_errors( self ) -> tuple[ str, ... ]:
        ''' Returns filenames containing error-level diagnostics. '''

    def exit_code( self ) -> int:
        ''' Returns appropriate exit code for CI/CD integration. '''

class ReportAssembler:
    ''' Assembles diagnostics into reports with deterministic ordering and context extraction. '''

    def __init__(
        self,
        include_contexts: __.typx.Annotated[
            bool, __.ddoc.Doc( 'Whether to extract source contexts.' ) ] = True,
        context_style: ContextStyle = ContextStyle.Highlighted,
        sort_diagnostics: __.typx.Annotated[
            bool, __.ddoc.Doc( 'Whether to sort diagnostics deterministically.' ) ] = True,
    ) -> None: ...

    def assemble_file_report(
        self,
        filename: str,
        violations: __.cabc.Sequence[ __.violations.Violation ],
        source_lines: __.cabc.Sequence[ str ],
        rule_registry: __.cabc.Mapping[ str, __.typx.Any ],
    ) -> __.typx.Annotated[
        Report,
        __.ddoc.Doc( 'Complete report for single file with enhanced diagnostics.' ),
    ]: ...

    def assemble_aggregate_report(
        self,
        file_reports: __.cabc.Sequence[ Report ],
    ) -> __.typx.Annotated[
        Report,
        __.ddoc.Doc( 'Aggregate report combining multiple file reports.' ),
    ]: ...

    def convert_violations_to_diagnostics(
        self,
        violations: __.cabc.Sequence[ __.violations.Violation ],
        rule_registry: __.cabc.Mapping[ str, __.typx.Any ],
    ) -> tuple[ Diagnostic, ... ]:
        ''' Converts core violations to enhanced diagnostics with metadata. '''

Pluggable Renderer System

Simple Renderer Protocol

The rendering system uses protocols for maximum flexibility with minimal complexity:

from . import __

# Type alias for display format identifiers
DisplayFormat: __.typx.TypeAlias = str

class RenderingOptions( __.immut.DataclassObject ):
    ''' Configuration for diagnostic rendering across formats. '''
    include_context: __.typx.Annotated[
        bool, __.ddoc.Doc( 'Whether to include source context in output.' ) ] = True
    context_size: __.typx.Annotated[
        int, __.ddoc.Doc( 'Lines of context around violations.' ) ] = 2
    include_rule_names: __.typx.Annotated[
        bool, __.ddoc.Doc( 'Whether to include descriptive rule names.' ) ] = True
    use_color: __.typx.Annotated[
        __.Absential[ bool ],
        __.ddoc.Doc( 'Color output (auto-detected if absent).' ) ] = __.absent

class Renderer( __.immut.Protocol ):
    ''' Protocol for diagnostic report rendering with consistent interface. '''

    def render_report(
        self, report: Report, options: RenderingOptions
    ) -> __.typx.Annotated[
        str, __.ddoc.Doc( 'Formatted report content for display.' ) ]: ...

class TextRenderer:
    ''' Human-readable text renderer with context display and color support. '''

    def render_report( self, report: Report, options: RenderingOptions ) -> str: ...

    def format_diagnostic(
        self, diagnostic: Diagnostic, include_context: bool = True
    ) -> tuple[ str, ... ]:
        ''' Formats individual diagnostic with optional context. '''

    def format_summary( self, report: Report ) -> str:
        ''' Formats summary line with diagnostic counts. '''

class JsonRenderer:
    ''' Structured JSON renderer for programmatic consumption. '''

    def render_report( self, report: Report, options: RenderingOptions ) -> str: ...

    def serialize_diagnostic(
        self, diagnostic: Diagnostic
    ) -> __.immut.Dictionary[ str, __.typx.Any ]:
        ''' Converts diagnostic to JSON-serializable structure. '''

class CompactRenderer:
    ''' Space-efficient text renderer for constrained output environments. '''

    def render_report( self, report: Report, options: RenderingOptions ) -> str: ...

def validate_renderer( renderer: __.typx.Any ) -> Renderer:
    ''' Validates that object implements Renderer protocol. '''
    if not isinstance( renderer, Renderer ):
        raise TypeError( f"Expected Renderer, got {type( renderer ).__name__}." )
    return renderer

# Type alias for renderer registry
RendererRegistry: __.typx.TypeAlias = __.accret.ValidatorDictionary[ DisplayFormat, Renderer ]

def create_renderer_registry( ) -> RendererRegistry:
    ''' Creates validated registry for display format renderers. '''
    return __.accret.ValidatorDictionary(
        value_validator = validate_renderer
    )

Output Management

Simple Output Handling

Basic output management for essential use cases:

from . import __

def print_content(
    content: str,
    stream: __.typx.TextIO,
) -> None:
    ''' Prints content to stream (stdout, stderr, files opened as streams). '''

def detect_stream_color_support( stream: __.typx.TextIO ) -> bool:
    ''' Detects whether stream supports color output. '''

Integration Interface

Main Diagnostic System

The primary interface connecting the diagnostic system to the linter framework:

from . import __

def create_report_from_violations(
    violations: __.cabc.Sequence[ __.violations.Violation ],
    source_lines_by_file: __.cabc.Mapping[ str, __.cabc.Sequence[ str ] ],
    rule_registry: __.cabc.Mapping[ str, __.typx.Any ],
) -> __.typx.Annotated[
    Report,
    __.ddoc.Doc( 'Enhanced diagnostic report from linter violations.' ),
]:
    ''' Creates diagnostic report from linter violations with enhanced metadata. '''

def render_report_with_format(
    report: Report,
    format_name: DisplayFormat,
    renderer_registry: RendererRegistry,
    options: RenderingOptions,
) -> __.typx.Annotated[
    str,
    __.ddoc.Doc( 'Formatted report in specified display format.' ),
]:
    ''' Renders report using specified format and options. '''

def print_report(
    report: Report,
    format_name: DisplayFormat,
    stream: __.typx.TextIO,
    renderer_registry: RendererRegistry,
    options: RenderingOptions,
) -> None:
    ''' Renders and prints report to stream (stdout, stderr, files opened as streams). '''

Module Organization

Module Structure

The diagnostic framework uses minimal module organization:

sources/vibelinter/reporting/
├── __.py                        # Reporting imports
├── __init__.py                  # Package entry point
├── diagnostics.py               # Diagnostic and Report structures
├── context.py                   # SourceContext and ContextExtractor
├── renderers.py                 # All renderer implementations
├── assembly.py                  # ReportAssembler implementation
├── printers.py                  # Stream printing utilities
└── integration.py               # Integration functions for linter framework

Import Organization

Import structure following established patterns:

# sources/vibelinter/reporting/__.py
from ..__ import *

# sources/vibelinter/reporting/__init__.py
from . import __
from .integration import create_report_from_violations, render_report_with_format, print_report
from .diagnostics import Diagnostic, Report, DiagnosticSeverity
from .renderers import DisplayFormat, RenderingOptions

Exception Handling

Minimal Exception Hierarchy

Simple exceptions following established patterns:

from . import __

class RenderFailure( __.Omnierror ):
    ''' Raised when report rendering fails. '''

class FormatNoSupport( __.Omnierror ):
    ''' Raised when requested display format is not supported. '''

Design Validation

Compliance Verification

The design maintains compliance with established practices:

Essential Features Preserved: - Multi-format output (text, JSON, compact, GitHub Actions) - Context display with validated POC patterns - Deterministic sorting for CI/CD consistency - Integration with existing Violation structures - Pluggable renderer architecture

Design Choices: - Simple string formatting for messages - Basic file/stream writing for output - Embedded metadata in Diagnostic structure - Single unified Report structure - Focus on essential formats (text, JSON, compact, GitHub Actions)

Architecture Benefits: - Clear integration path with existing linter framework - Simple protocol-based extension points - Validated context extraction patterns from POC - Sophisticated error reporting capabilities

Implementation Readiness: - Complete interface specifications for essential components - Clear upgrade path from basic violations to enhanced diagnostics - Proven context display patterns ready for implementation - Minimal viable sophistication meeting REQ-007 requirements

Architectural Separation Rationale

Component Boundary Design Decisions

The diagnostic system maintains deliberate architectural separations that serve different abstraction levels and responsibilities:

Violation vs. Diagnostic Separation

Design Decision: Maintain separation between Violation and Diagnostic structures.

Rationale: - Violation: Lightweight core data from rule execution focused on detection (what/where) - Diagnostic: Rich presentation metadata focused on display and guidance (how to present/what can be done) - Composition Pattern: Diagnostic embeds Violation to avoid duplication while adding presentation metadata - Performance: Rules generate lightweight violations without presentation overhead during analysis - Extensibility: Multiple presentation layers can enhance the same violations for different output formats

The embedded approach provides convenient access through proxy properties while maintaining clear separation of concerns between detection and presentation.

Configuration vs. Registry Separation

Design Decision: Maintain separation between Configuration and Registry systems.

Rationale: - Registry: Provides metadata about available rules (what exists, capabilities, instantiation patterns) - Configuration: Manages user preferences and runtime overrides (what to execute, with what parameters) - Command-Query Separation: Registry queries rule information, Configuration commands execution behavior - Independent Evolution: Registry changes with rule additions, Configuration changes with user requirements

This layered architecture enables independent development and testing of rule metadata management versus user preference application.

File Discovery vs. Engine Processing Separation

Design Decision: Maintain separation between File Discovery and Engine Processing systems.

Rationale: - File Discovery: Handles file system concerns (path resolution, filtering, gitignore integration) - Engine Processing: Handles analysis concerns (rule coordination, violation collection, performance optimization) - Single Responsibility: Discovery focuses on determining which files to analyze, Engine focuses on how to analyze them - Error Handling Isolation: Discovery handles path/permission errors, Engine handles parsing/analysis errors

Clear interfaces prevent mixing file system operations with analysis logic, enabling independent testing and optimization of each concern.

This design provides sophisticated error reporting capabilities with clear integration with the existing linter core framework while maintaining principled architectural separations.