diffview

package module
v0.0.0-...-544949e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 31, 2025 License: MIT Imports: 7 Imported by: 0

README

diffstory

diffstory walkthrough

A diff viewer designed for reviewing AI-generated code changes. Instead of line-by-line diffs, diffstory uses an LLM to classify changes into a structured narrative with semantic sections.

Installation

go install github.com/fwojciec/diffstory/cmd/diffstory@latest

Quick Start

# Set your Gemini API key
export GEMINI_API_KEY="your-api-key"

# Run in any git repository with changes
diffstory

Features

  • Git-native analysis - Auto-detects base branch from origin/HEAD and analyzes your current branch
  • LLM-powered classification - Uses Gemini to classify changes by type (bugfix, feature, refactor) and narrative pattern
  • Semantic sections - Groups related hunks by role (problem, fix, test, core, supporting)
  • Interactive TUI - Syntax-highlighted diff viewer with keyboard navigation
  • Eval case management - Save and replay analyzed diffs for evaluation

Usage

Analyze Current Branch
diffstory

Analyzes the diff between your current branch and its base branch, classifies it with Gemini, and opens an interactive TUI.

Replay Saved Cases
diffstory replay <file.jsonl> [index]

Re-opens a previously saved eval case. The index is zero-based and defaults to 0.

How It Works

  1. Detects your base branch from origin/HEAD
  2. Gets the diff (base...HEAD)
  3. Sends the diff to Gemini for classification
  4. Displays results in an interactive TUI with:
    • Change type and narrative pattern
    • Summary of changes
    • Sections grouping related hunks by semantic role

Requirements

  • Git repository with a configured remote
  • GEMINI_API_KEY environment variable

License

MIT

Documentation

Overview

Package diffview provides domain types for parsing and viewing diffs.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Analysis

type Analysis struct {
	Type    string          // e.g., "story", "security", "complexity"
	Payload json.RawMessage // Type-specific JSON payload
}

Analysis represents a single analysis result with a type discriminator.

type AnnotatedHunk

type AnnotatedHunk struct {
	ID   string // Unique identifier for referencing in analysis
	Hunk Hunk
}

AnnotatedHunk wraps a Hunk with an ID for LLM reference.

type ClassificationInput

type ClassificationInput struct {
	Repo          string        `json:"repo"`
	Branch        string        `json:"branch"`
	PRTitle       string        `json:"pr_title,omitempty"`
	PRDescription string        `json:"pr_description,omitempty"`
	Commits       []CommitBrief `json:"commits"`
	Diff          Diff          `json:"diff"`
}

ClassificationInput is the complete input for story classification. It represents a PR's worth of changes: multiple commits with their combined diff.

func (ClassificationInput) CaseID

func (c ClassificationInput) CaseID() string

CaseID returns a unique identifier for this case using repo/branch format. This uniquely identifies a PR-level case for judgment linking.

func (ClassificationInput) FirstCommitHash

func (c ClassificationInput) FirstCommitHash() string

FirstCommitHash returns the hash of the first commit, or empty if none. This is a migration helper - prefer iterating Commits directly.

func (ClassificationInput) FirstCommitMessage

func (c ClassificationInput) FirstCommitMessage() string

FirstCommitMessage returns the message of the first commit, or empty if none. This is a migration helper - prefer iterating Commits directly.

type Clipboard

type Clipboard interface {
	Copy(content string) error
}

Clipboard provides copy-to-clipboard functionality.

type Color

type Color string

Color is a hex string in "#RRGGBB" format (e.g., "#ff0000" for red). Empty string indicates no color (use terminal default).

type ColorPair

type ColorPair struct {
	Foreground string
	Background string
}

ColorPair represents a foreground and background color combination. Colors should be hex strings in "#RRGGBB" format (e.g., "#ff0000" for red). Empty strings are valid and indicate no color override (use terminal default).

type CommitBrief

type CommitBrief struct {
	Hash    string `json:"hash"`
	Message string `json:"message"`
	Diff    *Diff  `json:"diff,omitempty"`
}

CommitBrief captures essential commit metadata for PR context.

type DefaultFormatter

type DefaultFormatter struct{}

DefaultFormatter implements PromptFormatter with the standard format.

func (*DefaultFormatter) Format

func (f *DefaultFormatter) Format(input ClassificationInput) string

Format renders the classification input as structured text.

type Diff

type Diff struct {
	Files []FileDiff
}

Diff represents a complete diff containing one or more file changes.

type DiffAnalysis

type DiffAnalysis struct {
	Version  int        // Schema version for forward compatibility
	Analyses []Analysis // Multiple analysis types can be included
}

DiffAnalysis is an extensible container for diff analyses.

type EvalCase

type EvalCase struct {
	Input ClassificationInput  `json:"input"` // The input for classification
	Story *StoryClassification `json:"story"` // The LLM-generated classification (nil if not yet classified)
}

EvalCase represents a case for evaluation: a diff with its LLM-generated classification.

type EvalCaseLoader

type EvalCaseLoader interface {
	Load(path string) ([]EvalCase, error)
}

EvalCaseLoader loads evaluation cases from a source.

type EvalCaseSaver

type EvalCaseSaver interface {
	Save(path string, c EvalCase) error
}

EvalCaseSaver appends eval cases to a file.

type FileDiff

type FileDiff struct {
	OldPath   string      // "a/file.go" or empty for new files
	NewPath   string      // "b/file.go" or empty for deleted files
	Operation FileOp      // Added, Deleted, Modified, Renamed, Copied
	IsBinary  bool        // Binary files have no hunks
	OldMode   fs.FileMode // 0 if unchanged
	NewMode   fs.FileMode // For permission changes
	Hunks     []Hunk
	Extended  []string // Raw extended headers for passthrough
}

FileDiff represents changes to a single file.

func (FileDiff) Stats

func (f FileDiff) Stats() (added, deleted int)

Stats returns the number of added and deleted lines in the file.

type FileOp

type FileOp int

FileOp represents the type of operation performed on a file.

const (
	FileModified FileOp = iota
	FileAdded
	FileDeleted
	FileRenamed
	FileCopied
)

File operation types.

type GitRunner

type GitRunner interface {
	// Log returns commit hashes from the repository at repoPath, limited to n commits.
	// Deprecated: Use MergeCommits for PR-level extraction.
	Log(ctx context.Context, repoPath string, limit int) ([]string, error)
	// Show returns the diff for a specific commit hash.
	// Deprecated: Use DiffRange for PR-level extraction.
	Show(ctx context.Context, repoPath string, hash string) (string, error)
	// Message returns the commit message for a specific commit hash.
	// Deprecated: Use CommitsInRange for PR-level extraction.
	Message(ctx context.Context, repoPath string, hash string) (string, error)

	// MergeCommits returns merge commit hashes from the repository, limited to n commits.
	// Used to find PR boundaries in git history.
	MergeCommits(ctx context.Context, repoPath string, limit int) ([]string, error)
	// CommitsInRange returns commits between base and head (base exclusive, head inclusive).
	// For a merge commit, use merge^1..merge^2 to get all PR commits.
	CommitsInRange(ctx context.Context, repoPath, base, head string) ([]CommitBrief, error)
	// DiffRange returns the combined diff between base and head.
	// Uses three-dot notation (base...head) to show changes introduced by head since common ancestor.
	DiffRange(ctx context.Context, repoPath, base, head string) (string, error)
	// Diff returns the diff for a raw range specification (e.g., "main...feature" or "HEAD~3..HEAD").
	// The rangeSpec is passed directly to git diff, supporting both two-dot and three-dot notation.
	Diff(ctx context.Context, repoPath, rangeSpec string) (string, error)
	// CurrentBranch returns the name of the currently checked out branch.
	CurrentBranch(ctx context.Context, repoPath string) (string, error)
	// MergeBase returns the best common ancestor commit between two refs.
	MergeBase(ctx context.Context, repoPath, ref1, ref2 string) (string, error)
	// DefaultBranch returns the default branch name from origin/HEAD.
	// Returns an error if no remote is configured.
	DefaultBranch(ctx context.Context, repoPath string) (string, error)
}

GitRunner provides access to git operations for extracting commit history.

type Hunk

type Hunk struct {
	OldStart int    // From @@ -X,...
	OldCount int    // From @@ -X,Y ...
	NewStart int    // From @@ ...,+X
	NewCount int    // From @@ ...,+X,Y
	Section  string // Optional function name after @@ ... @@
	Lines    []Line
}

Hunk represents a contiguous block of changes within a file.

type HunkRef

type HunkRef struct {
	File         string `json:"file"`
	HunkIndex    int    `json:"hunk_index"`
	Category     string `json:"category"`                // refactoring, systematic, core, noise
	Collapsed    bool   `json:"collapsed"`               // Whether to collapse in viewer
	CollapseText string `json:"collapse_text,omitempty"` // Summary when collapsed
}

HunkRef references a specific hunk with classification metadata.

type Judgment

type Judgment struct {
	CaseID   string    `json:"case_id"`   // Links to EvalCase.Input.CaseID() (repo/branch)
	Index    int       `json:"index"`     // Position in input file (0-based)
	Judged   bool      `json:"judged"`    // Whether pass/fail has been explicitly set
	Pass     bool      `json:"pass"`      // Whether the classification is acceptable
	Critique string    `json:"critique"`  // Explanation for failure (empty if pass)
	JudgedAt time.Time `json:"judged_at"` // When judgment was recorded
}

Judgment represents a human reviewer's evaluation of an EvalCase.

type JudgmentStore

type JudgmentStore interface {
	Load(path string) ([]Judgment, error)
	Save(path string, judgments []Judgment) error
}

JudgmentStore persists and retrieves judgments.

type LanguageDetector

type LanguageDetector interface {
	// DetectFromPath returns the language name for the given path,
	// or an empty string if the language cannot be determined.
	// Accepts paths with or without "a/" or "b/" prefixes (common in diffs).
	DetectFromPath(path string) string
}

LanguageDetector determines the programming language from a file path.

type Line

type Line struct {
	Type       LineType
	Content    string
	OldLineNum int  // 0 if line is Added
	NewLineNum int  // 0 if line is Deleted
	NoNewline  bool // "\ No newline at end of file" marker
}

Line represents a single line within a hunk.

type LineType

type LineType int

LineType represents the type of a diff line.

const (
	LineContext LineType = iota
	LineAdded
	LineDeleted
)

Line types.

type Palette

type Palette struct {
	// Base colors
	Background Color // Primary background
	Foreground Color // Primary foreground/text

	// Diff colors
	Added    Color // Added lines and text
	Deleted  Color // Deleted lines and text
	Modified Color // Modified content
	Context  Color // Unchanged context lines

	// Syntax highlighting colors
	Keyword     Color // Language keywords (if, for, func, etc.)
	String      Color // String literals
	Number      Color // Numeric literals
	Comment     Color // Comments
	Operator    Color // Operators (+, -, =, etc.)
	Function    Color // Function names
	Type        Color // Type names
	Constant    Color // Constants and boolean literals
	Punctuation Color // Brackets, semicolons, etc.

	// UI colors
	UIBackground Color // Secondary background (panels, sidebars)
	UIForeground Color // Secondary foreground (dimmed text)
	UIAccent     Color // Accent color (highlights, focus)
}

Palette defines semantic colors for a theme. All colors are hex strings in "#RRGGBB" format.

type Parser

type Parser interface {
	// Parse reads diff content and returns the parsed result.
	Parse(r io.Reader) (*Diff, error)
}

Parser parses diff content into domain types.

type PromptFormatter

type PromptFormatter interface {
	Format(input ClassificationInput) string
}

PromptFormatter renders classification input as structured text for LLM prompts.

type RubricJudge

type RubricJudge interface {
	// Judge evaluates whether the output satisfies the given criterion.
	Judge(ctx context.Context, criterion, output string) (*RubricResult, error)
}

RubricJudge evaluates text output against natural language criteria. Used for LLM-as-judge testing patterns.

type RubricResult

type RubricResult struct {
	Passed    bool   // Whether the output satisfied the criterion
	Reasoning string // LLM's explanation for the judgment
}

RubricResult represents the outcome of an LLM-as-judge evaluation.

type Section

type Section struct {
	Role        string    `json:"role"`        // problem, fix, test, core, supporting, etc.
	Title       string    `json:"title"`       // Human-readable section title
	Hunks       []HunkRef `json:"hunks"`       // References to hunks in this section
	Explanation string    `json:"explanation"` // Why this section matters
}

Section groups related hunks with a narrative role.

type Segment

type Segment struct {
	Text    string // The text content of this segment
	Changed bool   // True if this segment differs between old/new versions
}

Segment represents a portion of text within a line for word-level diffing. Used to highlight specific changed words/characters within modified lines.

type StoryAnalysis

type StoryAnalysis struct {
	ChangeType string      `json:"change_type"` // e.g., "refactor", "feature", "bugfix"
	Summary    string      `json:"summary"`     // One-line description of what changed
	Parts      []StoryPart `json:"parts"`       // Ordered sequence of change components
}

StoryAnalysis describes the narrative structure of a code change.

type StoryClassification

type StoryClassification struct {
	ChangeType string    `json:"change_type"`         // bugfix, feature, refactor, chore, docs
	Narrative  string    `json:"narrative"`           // cause-effect, core-periphery, before-after, etc.
	Summary    string    `json:"summary"`             // One sentence describing the change
	Sections   []Section `json:"sections"`            // Ordered sections grouping related hunks
	Evolution  string    `json:"evolution,omitempty"` // How changes evolved across commits
}

StoryClassification is the LLM's structured output for a diff.

type StoryClassifier

type StoryClassifier interface {
	Classify(ctx context.Context, input ClassificationInput) (*StoryClassification, error)
}

StoryClassifier produces structured classification from diff + commit info.

type StoryGenerator

type StoryGenerator interface {
	// Generate creates a DiffAnalysis from annotated hunks.
	Generate(ctx context.Context, hunks []AnnotatedHunk) (*DiffAnalysis, error)
}

StoryGenerator generates narrative analyses for code changes.

type StoryPart

type StoryPart struct {
	Role        string   `json:"role"`        // e.g., "setup", "core", "cleanup"
	HunkIDs     []string `json:"hunk_ids"`    // References to AnnotatedHunk IDs
	Explanation string   `json:"explanation"` // Human-readable description of this part
}

StoryPart represents one component of a change story.

type Style

type Style struct {
	Foreground string // Hex color code (e.g., "#ff0000") or empty for default
	Bold       bool   // Whether the text should be bold
}

Style represents the visual styling for a token.

type Styles

type Styles struct {
	Added            ColorPair // Style for added lines (+)
	Deleted          ColorPair // Style for deleted lines (-)
	Context          ColorPair // Style for context lines (unchanged)
	HunkHeader       ColorPair // Style for hunk headers (@@ ... @@)
	FileHeader       ColorPair // Style for file headers (--- a/... +++ b/...)
	FileSeparator    ColorPair // Style for separator lines between files
	LineNumber       ColorPair // Style for line numbers in the gutter (context lines)
	AddedGutter      ColorPair // Style for gutter on added lines (stronger background)
	DeletedGutter    ColorPair // Style for gutter on deleted lines (stronger background)
	AddedHighlight   ColorPair // Style for changed text within added lines (word-level diff)
	DeletedHighlight ColorPair // Style for changed text within deleted lines (word-level diff)
}

Styles contains color pairs for all visual elements in a diff.

type Theme

type Theme interface {
	Styles() Styles
	Palette() Palette
}

Theme provides styles for rendering diffs. Different implementations can provide light/dark variants.

type Token

type Token struct {
	Text  string // The text content of this token
	Style Style  // Visual style to apply (colors, bold, etc.)
}

Token represents a syntax-highlighted segment of code.

type Tokenizer

type Tokenizer interface {
	// Tokenize splits source code into syntax-highlighted tokens for the given language.
	// Returns nil if the language is not supported.
	Tokenize(language, source string) []Token

	// TokenizeLines tokenizes multi-line source code with full context,
	// returning tokens split by line. This correctly handles multi-line
	// constructs like /* */ comments and JSDoc.
	// Returns nil if the language is not supported.
	TokenizeLines(language, source string) [][]Token
}

Tokenizer extracts syntax tokens from source code.

type ValidationError

type ValidationError struct {
	Section   int              // Index of the section containing the error
	HunkRef   HunkRef          // The problematic hunk reference
	Reason    ValidationReason // Why this reference is invalid
	HunkCount int              // Actual hunk count for the file (for invalid_index errors)
}

ValidationError describes a single validation failure in a classification.

func ValidateClassification

func ValidateClassification(diff *Diff, classification *StoryClassification) []ValidationError

ValidateClassification checks that all hunk references in a classification are valid for the given diff. Returns a slice of validation errors, or nil if the classification is valid.

func (ValidationError) Error

func (e ValidationError) Error() string

Error implements the error interface.

type ValidationReason

type ValidationReason string

ValidationReason identifies why a HunkRef is invalid.

const (
	ErrInvalidHunkIndex ValidationReason = "invalid_index"
	ErrFileNotFound     ValidationReason = "file_not_found"
)

Validation error reasons.

type Viewer

type Viewer interface {
	// View displays the diff and blocks until the user exits.
	View(ctx context.Context, diff *Diff) error
}

Viewer displays a diff to the user.

type WordDiffer

type WordDiffer interface {
	// Diff returns segments for both the old and new strings,
	// marking which portions changed between them.
	Diff(old, new string) (oldSegs, newSegs []Segment)
}

WordDiffer computes word-level differences between two strings.

Directories

Path Synopsis
Package bubbletea provides a terminal UI viewer for diffs using the Bubble Tea framework.
Package bubbletea provides a terminal UI viewer for diffs using the Bubble Tea framework.
Package chroma provides syntax highlighting using the chroma library.
Package chroma provides syntax highlighting using the chroma library.
Package clipboard provides clipboard operations via platform-specific commands.
Package clipboard provides clipboard operations via platform-specific commands.
cmd
diffstory command
diffview command
evalreview command
Package eval provides test helpers for LLM-as-judge evaluation patterns.
Package eval provides test helpers for LLM-as-judge evaluation patterns.
Package gemini provides a StoryGenerator implementation using the Google Gemini API.
Package gemini provides a StoryGenerator implementation using the Google Gemini API.
Package git provides access to git operations via shell commands.
Package git provides access to git operations via shell commands.
Package gitdiff implements diff parsing using bluekeyes/go-gitdiff.
Package gitdiff implements diff parsing using bluekeyes/go-gitdiff.
Package jsonl provides JSONL file handling for eval cases and judgments.
Package jsonl provides JSONL file handling for eval cases and judgments.
Package lipgloss provides theme implementations using the Lipgloss styling library.
Package lipgloss provides theme implementations using the Lipgloss styling library.
Package mock provides test doubles for diffview interfaces.
Package mock provides test doubles for diffview interfaces.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL