Git's merge driver: line-level
Git's built-in merge works at the line level. When you run git merge, git compares the base version with both branches line by line. It uses a diff algorithm (Myers diff by default) that finds the longest common subsequence of lines, then identifies which lines were added, removed, or changed in each branch.
The critical concept is hunks — contiguous groups of changed lines. Git identifies hunks in each branch, then checks if any hunks from the two branches overlap or are adjacent. If they do, git declares a conflict, even if the actual changes are logically independent.
weave-driver: entity-level
weave-driver replaces git's line-level comparison with entity-level comparison. Instead of "which lines changed?", weave asks "which functions/classes/properties changed?"
Here's the key difference:
- Parsing step: weave runs sem-core (tree-sitter based) on all three versions of the file (base, ours, theirs) to extract named entities — functions, classes, methods, interfaces, types, properties, etc.
- Matching step: Entities are matched across the three versions by their stable ID (type + name + parent).
function::processDatain base is matched tofunction::processDatain ours and theirs. - Per-entity resolution: Each entity is resolved independently. If only one branch modified
processData, that version wins. If only the other branch modifiedvalidateInput, that version wins. No conflict — they're different entities. - Fallback: If both branches modified the same entity differently, weave falls back to
diffy::merge(3-way line merge) on just that entity's body. So you only get a conflict when two branches genuinely changed the same function in incompatible ways.
Why this matters for AI agents
This distinction becomes critical in multi-agent workflows. When you have two or more AI agents working on the same codebase:
- Agent A is told to refactor
processData()to use async/await - Agent B is told to add validation to
validateInput()
Both agents might work in the same file. With git, even though they're editing completely different functions, the merge will likely conflict because the changed line ranges are close together. Someone (or something) has to manually resolve the "conflict" which isn't actually a conflict at all.
With weave, the merge is automatic. weave sees that Agent A changed entity function::processData and Agent B changed entity function::validateInput. Different entities, no conflict, merge cleanly.
A concrete example
Consider this TypeScript file:
export function processData(input: string) { return input.trim(); } export function validateInput(data: unknown) { return data !== null; }
Agent A changes processData:
export async function processData(input: string) { const cleaned = input.trim(); const result = await transform(cleaned); return result; } export function validateInput(data: unknown) { return data !== null; }
Agent B changes validateInput:
export function processData(input: string) { return input.trim(); } export function validateInput(data: unknown) { if (data === null || data === undefined) { throw new Error("Input required"); } return true; }
Git's result
CONFLICT (content): Merge conflict in src/lib.ts
The expanded processData pushes lines down, making the hunk ranges overlap with the changed validateInput. Git can't tell them apart — it just sees overlapping changed lines.
Weave's result
2 entities matched, 2 modified, 0 conflicts
weave sees: function::processData changed in ours only → use ours. function::validateInput changed in theirs only → use theirs. Clean merge with both changes preserved.
The mental model
Think of it this way:
- Git sees a file as a list of lines. Any change is a line change. Proximity = potential conflict.
- Weave sees a file as a list of named entities. Each entity is resolved independently. Only same-entity, different-content changes conflict.
This is the same conceptual leap that git made over raw patches — operating at a higher semantic level to make merging smarter and less painful.