# Attribution: Scope Mixed to the Touching Session and Unify Authorship Rule

`a17eb09` → [main](/content/gh/entireio/cli/commits/main/index.html)  
  
Soph · 1mo ago · 2 files · +71 added / -17 removed

Two related correctness bugs in line attribution:

1. `entire blame` and `entire why` disagreed on the tag. resolveLine marks a line **[MX]** only when its *preferred* candidate is mixed, but the why-time enrichment path (enrichAttributionLineWithFetch) marked it **[MX]** if *any* candidate was mixed. A line with a non-mixed preferred candidate and a mixed secondary one rendered **[AI]** in blame and **[MX]** in why. Extract a single authorshipForPreferred() rule used by both paths.

2. Mixed was checkpoint-scoped, not session-scoped. readCheckpointContext OR-combined the checkpoint-wide CombinedAttribution with every session's attribution, so a line from an agent-only file got tagged **[MX]** whenever the checkpoint *also* touched a hand-edited file. Scope Mixed to the session whose work actually touched this file; fall back to the checkpoint-wide attribution only when no session metadata resolves.

Adds a regression test where the file-touching session is pure-AI but the checkpoint's combined attribution is mixed — the line must stay **[AI]**.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

## Sessions

ef513bf77157 View transcript

### Attribution Bugs and Refactoring in CLI

### Changes

- cmd/entire/cli  
  - Mattribution.go +31 / -17
  - Mattribution_test.go +40

"""
360 unmodified lines

"""

// authorshipForPreferred maps the preferred candidate to a line's authorship.
// A committed line that carries a checkpoint trailer is **[AI]**; it is **[MX]** only
// when the candidate that actually produced it (the session whose work touched
// this file) reflects mixed AI+human work.
func authorshipForPreferred(preferred *attributionCandidate) attributionAuthorship {
if preferred != nil && preferred.Mixed {
return attributionMixed
}
return attributionAI
}

func preferredAttributionCandidate(candidates []attributionCandidate, file string) *attributionCandidate {
if len(candidates) == 0 {
return nil
}
