Implement line-level and block-level blame functionality similar to git blame, but for AI-assisted edits. This will allow users to see which AI model modified specific lines or blocks of code, with timestamp and context information.
ai-blame.rs currently:
- Extracts file-level provenance from AI agent execution traces
- Supports multiple output modes (append, sidecar, comment)
- Tracks creation and modification events at the file level
- Stores edit history with timestamp, model, and action metadata
- Line-level tracking: Associate each line with the AI model that last modified it
- Block-level tracking: Group related changes into logical blocks
- Interactive display: Provide a
git blame-like interface showing provenance alongside code - Historical view: Show the evolution of specific lines/blocks over time
The current Claude Code trace payload already contains enough information for a useful MVP:
toolUseResult.oldString/toolUseResult.newString(exact replacement strings)toolUseResult.type == "create"withtoolUseResult.content(full file content on create)toolUseResult.structuredPatch(sometimes present; may include unified diff-style hunks)
Key implication: we can implement blame without adding heavy dependencies (no SQLite, rope, or tree-sitter) by using a reverse-apply strategy over oldString/newString.
Given current file content (from disk) and a time-ordered list of edits from traces:
- Start with current file lines; mark all lines as unassigned.
- Iterate edits from newest → oldest.
- For each edit:
- If it is a
create, assign any still-unassigned lines to that event (then stop). - If it is a replace edit, locate the edit’s
newStringin the current working string.- If found, assign the corresponding line span to that event only where still unassigned.
- Reverse-apply by replacing that occurrence of
newStringwitholdString.
- If it is a
- The result is a per-line “last AI event that introduced this exact text”.
This yields a practical git blame-like attribution for the current working tree.
Block-level blame can be derived from line-level blame via a simple, effective rule:
- Block = maximal consecutive run of lines attributed to the same event (same timestamp/model/session).
This gives stable, explainable block boundaries without needing syntax parsing.
- If
newStringoccurs multiple times, we choose the “best” match (prefer matches near any hunk line number instructuredPatchwhen available; otherwise pick the first). - If we can’t locate
newString, we skip that reverse-apply step; attribution may beunknownfor some lines. - Deletions (where
newStringis empty) don’t affect blame for existing lines; we may skip reversing those edits.
edit_history:
- timestamp: "2025-12-01T08:03:42+00:00"
model: claude-opus-4-5-20251101
agent_tool: claude-code
action: CREATEDFor MVP, prefer storing line/blame data in sidecars (to avoid modifying code files) and keep file-level edit_history as-is.
line_provenance:
- line_range: [1, 5]
timestamp: "2025-12-01T08:03:42+00:00"
model: claude-opus-4-5-20251101
agent_tool: claude-code
action: CREATED
- line_range: [10, 15]
timestamp: "2025-12-02T14:22:10+00:00"
model: claude-sonnet-4-20250514
agent_tool: claude-code
action: MODIFIED
# Optional debug fields for matching/uncertainty
match_quality: exact | ambiguous | missing
structured_patch: "@@ -12,2 +12,3 @@ ..."// src/models.rs additions
/// Represents a line or range of lines in a file
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LineRange {
pub start: usize,
pub end: usize,
}
/// Line-level provenance information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LineProvenance {
pub line_range: LineRange,
pub timestamp: DateTime<Utc>,
pub model: String,
pub agent_tool: String,
pub action: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub match_quality: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub structured_patch: Option<String>,
}
/// Block-level grouping of related changes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockProvenance {
pub block_id: String,
pub line_ranges: Vec<LineRange>,
pub timestamp: DateTime<Utc>,
pub model: String,
pub agent_tool: String,
pub description: Option<String>,
pub related_files: Vec<String>,
}Goal: Capture enough data from traces to compute blame reliably.
Tasks:
- Extend trace parsing to capture:
oldString,newString,structuredPatchcontentfor creates
- Preserve backwards compatibility for existing
annotatebehavior.
Testing:
- Unit tests proving we still parse timestamps/models and now also capture replacement payload.
Goal: Provide git blame-like output for current working tree.
Tasks:
- Implement reverse-apply blame computation.
- Implement block grouping from consecutive line attributions.
- Add CLI:
ai-blame show <file> [--lines A-B] [--blocks].
Tasks:
- Extend
annotateto optionally writeline_provenance/block_provenanceto sidecars (config-driven). - Add richer matching/uncertainty reporting.
- Add history/diff style commands once we have a stable on-disk schema.
Output Format:
model timestamp line | code
-----------------------------------------------------------------------------
claude-opus-4 2025-12-01 08:03 1 | use ai_blame::cli;
claude-opus-4 2025-12-01 08:03 2 |
claude-sonnet-4 2025-12-02 14:22 3 | fn main() {
claude-sonnet-4 2025-12-02 14:22 4 | if let Err(e) = cli::run() {
claude-opus-4 2025-12-01 08:03 5 | eprintln!("Error: {}", e);
Problem: Line numbers change as code is edited Solution:
- Maintain line offset mappings for each edit
- Use content hashing to identify moved/renamed lines
- Store historical line mappings for accurate blame
Problem: Multiple AI edits to same lines Solution:
- Track conflict detection and resolution
- Show all contributors when lines conflict
- Maintain provenance through conflict resolution
Problem: Large files and many traces slow down analysis Solution:
- Incremental processing of new traces only
- Cache parsed blame data
- Use efficient data structures (interval trees for line ranges)
- Lazy loading of detailed provenance
Problem: AI traces may not capture all changes accurately Solution:
- Combine with actual file diffs for verification
- Detect and mark uncertain attributions
- Allow manual corrections/annotations
No new crates required for the MVP; implement reverse-apply matching using String operations.
- Diff parsing edge cases (empty lines, large changes, binary files)
- Line mapping with insertions, deletions, and moves
- Blame query API with various filters
- Block detection algorithm accuracy
- Process real Claude Code traces
- Generate blame output for test projects
- Verify blame accuracy against known edits
- Test backward compatibility with file-level system
- Benchmark blame generation for large files (10k+ lines)
- Test database query performance with many traces
- Memory usage for large codebases
- Update README.md with line-level blame examples
- Create tutorial: "Understanding Your AI Code History"
- Add cookbook: Common blame queries and analysis
- Architecture overview of blame system
- Data format specifications
- API reference for blame queries
- Guide for extending to new agent types
- Accuracy: >95% of lines correctly attributed to AI model
- Performance: Blame generation <1s for files up to 1000 lines
- Usability: Users can quickly understand which AI modified what
- Coverage: Support for all file types ai-blame already handles
- Web-based visualization: Interactive blame viewer in browser
- IDE integration: Show blame inline in VS Code, IntelliJ, etc.
- Blame statistics: Dashboard showing AI contribution metrics
- Blame export: Generate reports in various formats (HTML, PDF, CSV)
- Cross-file blame: Track changes that span multiple files
- Semantic blame: Attribute logical changes (feature additions, bug fixes) rather than just line changes
- Machine learning: Improve attribution accuracy using ML models
- Natural language queries: "Show me what Claude changed last week"
- Blame prediction: Predict which AI model would make specific changes
- Collaboration tracking: Track AI-human collaboration patterns
This plan is intentionally MVP-first and should be implementable quickly:
- Phase 1: enrich trace extraction
- Phase 2: implement
show(line + block display) using reverse-apply - Phase 3: optional persistence/integration with
annotate
- Should we store complete file history or just line-level diffs?
- How to handle binary files or files with no line structure?
- What level of detail to include in sidecar vs. inline comments?
- How to represent uncertainty in attribution?
- Should block detection be syntax-aware (using tree-sitter) or heuristic-based?
- git blame documentation
- similar crate - Diff algorithms
- tree-sitter - Syntax-aware parsing
- Current ai-blame.rs codebase structure