<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Rohan Sharma</title>
    <description>The latest articles on Forem by Rohan Sharma (@rohan_sharma_2003).</description>
    <link>https://forem.com/rohan_sharma_2003</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3760809%2F42b17c69-43b4-444a-adf2-a6b8fd88984f.png</url>
      <title>Forem: Rohan Sharma</title>
      <link>https://forem.com/rohan_sharma_2003</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rohan_sharma_2003"/>
    <language>en</language>
    <item>
      <title>I built a semantic diff that understands functions, not just lines</title>
      <dc:creator>Rohan Sharma</dc:creator>
      <pubDate>Thu, 09 Apr 2026 16:57:18 +0000</pubDate>
      <link>https://forem.com/rohan_sharma_2003/i-built-a-semantic-diff-that-understands-functions-not-just-lines-2jkh</link>
      <guid>https://forem.com/rohan_sharma_2003/i-built-a-semantic-diff-that-understands-functions-not-just-lines-2jkh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvusyl1sms3tzwchvlyuz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvusyl1sms3tzwchvlyuz.png" alt=" " width="800" height="867"&gt;&lt;/a&gt;git diff shows you lines. But when you're reviewing code, you think in functions, classes, and methods.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/Ataraxy-Labs/sem" rel="noopener noreferrer"&gt;https://github.com/Ataraxy-Labs/sem&lt;/a&gt;, a CLI that uses tree-sitter to break source code into semantically meaningful chunks and diff them as individual entities.&lt;/p&gt;

&lt;p&gt;What it looks like?&lt;/p&gt;

&lt;p&gt;On a recent commit that added Dart language support, git diff showed x lines of changes across lock files and source code. sem diff showed this:&lt;/p&gt;

&lt;p&gt;crates/sem-core/src/parser/plugins/code/entity_extractor.rs&lt;/p&gt;

&lt;p&gt;∆ function  find_name_byte_range     [modified]&lt;br&gt;
  ∆ function  visit_node               [modified]&lt;br&gt;
  ⊖ function  extract_name             [deleted]&lt;br&gt;
  ⊕ function  walk_dart_class_member   [added]&lt;br&gt;
  ⊕ function  map_class_member_type    [added]&lt;/p&gt;

&lt;p&gt;5 entities changed. That's what a reviewer actually needs to know.&lt;/p&gt;

&lt;p&gt;Impact analysis&lt;/p&gt;

&lt;p&gt;The part I find most useful: point it at any function and it shows everything that depends on it, transitively, across the whole repo.&lt;/p&gt;

&lt;p&gt;$ sem impact visit_node&lt;/p&gt;

&lt;p&gt;→ depends on: 13 functions&lt;br&gt;
  ← depended on by: extract_entities, extract_ocaml_named_bindings&lt;br&gt;
! 2 entities transitively affected&lt;/p&gt;

&lt;p&gt;Before you refactor something, you know exactly what's downstream.&lt;/p&gt;

&lt;p&gt;Commands&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sem diff - entity-level diff with word-level highlights&lt;/li&gt;
&lt;li&gt;sem entities - list all entities in a file with line ranges&lt;/li&gt;
&lt;li&gt;sem impact - show what breaks if an entity changes&lt;/li&gt;
&lt;li&gt;sem blame - git blame at the entity level&lt;/li&gt;
&lt;li&gt;sem log - track how an entity evolved over time&lt;/li&gt;
&lt;li&gt;sem context - token-budgeted context for LLMs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supports 20+ languages (Rust, Python, TypeScript, Go, Java, C, C++, Ruby, Swift, Kotlin, and more). Written in Rust. Open source.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/Ataraxy-Labs/sem" rel="noopener noreferrer"&gt;https://github.com/Ataraxy-Labs/sem&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>rust</category>
      <category>git</category>
      <category>programming</category>
    </item>
    <item>
      <title>Git's Line-Based Merge is Broken for the AI Agent Era</title>
      <dc:creator>Rohan Sharma</dc:creator>
      <pubDate>Mon, 09 Feb 2026 00:52:34 +0000</pubDate>
      <link>https://forem.com/rohan_sharma_2003/why-gits-merge-algorithm-fails-on-52-of-concurrent-edits-and-how-to-fix-it-2oaj</link>
      <guid>https://forem.com/rohan_sharma_2003/why-gits-merge-algorithm-fails-on-52-of-concurrent-edits-and-how-to-fix-it-2oaj</guid>
      <description>&lt;p&gt;Git's merge algorithm was designed in 2005 for a world where humans worked on separate files. It compares lines. That worked fine for two decades. But now we have AI agents making parallel changes to the same codebase, and the cracks are showing.&lt;/p&gt;

&lt;p&gt;I ran a benchmark: 31 merge scenarios where two branches make independent, non-conflicting changes to the same file. Git produced false conflicts on 16 of them. That's a 48% clean merge rate on changes that should all resolve automatically.&lt;/p&gt;

&lt;p&gt;The fix turned out to be simple: parse the code with tree-sitter, merge at the function/class level instead of the line level.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Git's 3-way merge actually works
&lt;/h2&gt;

&lt;p&gt;Git merge uses the diff3 algorithm. Given three versions of a file (base, ours, theirs), it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Computes a line-by-line diff from base to ours&lt;/li&gt;
&lt;li&gt;Computes a line-by-line diff from base to theirs&lt;/li&gt;
&lt;li&gt;Tries to apply both diffs to the base&lt;/li&gt;
&lt;li&gt;If both diffs touch the same line range, it declares a conflict&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key word is &lt;strong&gt;line range&lt;/strong&gt;. Git doesn't know what a function is, what a class is, or where one logical unit ends and another begins. It sees a flat sequence of text lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this breaks
&lt;/h2&gt;

&lt;p&gt;Consider this base file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Branch A adds a function below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Branch B also adds a function below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hashPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These changes are completely independent. &lt;code&gt;formatDate&lt;/code&gt; and &lt;code&gt;hashPassword&lt;/code&gt; have nothing to do with each other. But both branches inserted new lines at the same position (after line 3), so Git sees overlapping line ranges and produces:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
&lt;/span&gt;&lt;span class="p"&gt;export function formatDate(date: Date): string {
&lt;/span&gt;    return date.toISOString().split('T')[0];
}
&lt;span class="gh"&gt;=======
&lt;/span&gt;&lt;span class="p"&gt;export function hashPassword(password: string): string {
&lt;/span&gt;    return crypto.createHash('sha256').update(password).digest('hex');
}
&lt;span class="gi"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; feature-branch
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A human has to look at this, understand that both functions should exist, and manually combine them. In a real codebase with agents making dozens of parallel changes, these false conflicts pile up fast.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix: merge at the entity level
&lt;/h2&gt;

&lt;p&gt;The insight is straightforward: code has structure. Functions, classes, interfaces, JSON keys, YAML mappings. If you parse the code into semantic entities and merge at that granularity, independent changes to different entities can never conflict.&lt;/p&gt;

&lt;p&gt;Here's the algorithm:&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Parse all three versions into entities
&lt;/h3&gt;

&lt;p&gt;Using tree-sitter, extract every top-level entity from each version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base: &lt;code&gt;[validateToken]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ours: &lt;code&gt;[validateToken, formatDate]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Theirs: &lt;code&gt;[validateToken, hashPassword]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each entity has an identity (name + type + scope) and content (the full source text).&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Match entities across versions
&lt;/h3&gt;

&lt;p&gt;Build a mapping: which entity in base corresponds to which entity in ours and theirs? Match by identity. New entities (present in ours/theirs but not base) are additions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;validateToken&lt;/code&gt;: present in all three, unchanged in both branches&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;formatDate&lt;/code&gt;: added by ours only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hashPassword&lt;/code&gt;: added by theirs only&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 3: Resolve each entity independently
&lt;/h3&gt;

&lt;p&gt;For each entity, apply simple rules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;th&gt;Ours&lt;/th&gt;
&lt;th&gt;Theirs&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Keep (no change)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Modified&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Take ours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Modified&lt;/td&gt;
&lt;td&gt;Take theirs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Modified&lt;/td&gt;
&lt;td&gt;Modified&lt;/td&gt;
&lt;td&gt;Conflict (if different)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Added&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Include from ours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Added&lt;/td&gt;
&lt;td&gt;Include from theirs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Present&lt;/td&gt;
&lt;td&gt;Deleted&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Delete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the example: &lt;code&gt;validateToken&lt;/code&gt; is unchanged (keep it), &lt;code&gt;formatDate&lt;/code&gt; is added by ours (include it), &lt;code&gt;hashPassword&lt;/code&gt; is added by theirs (include it). Clean merge. No conflict.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Reconstruct the file
&lt;/h3&gt;

&lt;p&gt;Reassemble from the resolved entities, preserving the ours-side ordering with theirs-only additions inserted after their nearest neighbor:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hashPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Both functions included, no conflict markers, no human intervention needed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Handling the hard cases
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Same entity modified by both branches
&lt;/h3&gt;

&lt;p&gt;When both branches change the same function, entity-level merge can't always auto-resolve. But it still does better than Git. Instead of "lines 42-47 conflict," you get "function process was modified by both branches." Much more useful context.&lt;/p&gt;

&lt;p&gt;You can also attempt an intra-entity merge: run line-level diff3 on just the body of the conflicting function. If the changes are to different lines within the function, this resolves it. If they touch the same lines, you get a conflict scoped to that single function rather than a blob of the whole file.&lt;/p&gt;
&lt;h3&gt;
  
  
  Imports
&lt;/h3&gt;

&lt;p&gt;Imports sit between entities and need special handling. We treat them as a set: collect all imports from both branches, deduplicate, sort. This handles the common case where both branches add different imports.&lt;/p&gt;
&lt;h3&gt;
  
  
  Nested entities (classes with methods)
&lt;/h3&gt;

&lt;p&gt;Classes contain methods. We handle this by treating the class as a single entity and doing a recursive inner merge: match methods by name, resolve each independently. Branch A adds methodX and Branch B adds methodY to the same class? Clean merge.&lt;/p&gt;
&lt;h3&gt;
  
  
  Language support and fallback
&lt;/h3&gt;

&lt;p&gt;We support 15 languages via tree-sitter grammars (TypeScript, JavaScript, Python, Go, Rust, Java, C, C++, Ruby, C#, JSON, YAML, TOML, Markdown, TSX). For unsupported file types or binary files, we fall back to Git's default line-level merge. No worse than before.&lt;/p&gt;
&lt;h2&gt;
  
  
  The benchmark
&lt;/h2&gt;

&lt;p&gt;31 merge scenarios covering common cases: both sides add different functions, one modifies while the other adds, both modify different functions, JSON key additions, import additions, etc.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Driver&lt;/th&gt;
&lt;th&gt;Clean merges&lt;/th&gt;
&lt;th&gt;False conflicts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Entity-level (weave)&lt;/td&gt;
&lt;td&gt;31/31 (100%)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git (line-based)&lt;/td&gt;
&lt;td&gt;15/31 (48%)&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All 16 false conflicts from Git are cases where independent changes happen to be in the same line range. Entity-level merge resolves every single one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;This problem existed before AI agents, but it was manageable. Humans naturally avoid editing the same file at the same time, and when conflicts happen, they resolve them during PR review.&lt;/p&gt;

&lt;p&gt;With AI agents working in parallel, the math changes. If you have 3 agents making changes to a codebase, and 2 of them touch the same file (adding different functions), you get a false conflict every time. Scale that to 10 agents and it becomes the bottleneck. The agents can write code in seconds, but a human has to stop and resolve conflicts that shouldn't exist.&lt;/p&gt;

&lt;p&gt;Entity-level merge is a drop-in fix. Configure it as a Git merge driver, and &lt;code&gt;git merge&lt;/code&gt;, &lt;code&gt;git rebase&lt;/code&gt;, &lt;code&gt;git pull&lt;/code&gt; all use it automatically. No workflow changes, no new commands to learn.&lt;/p&gt;
&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ordering is heuristic.&lt;/strong&gt; When both branches add new entities, the output ordering is a best guess. For most code this doesn't matter (function order in a file is arbitrary), but for ordered structures like arrays, it could produce unexpected results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tree-sitter grammar quality varies.&lt;/strong&gt; We validate the merged output by re-parsing it and checking that the AST is valid. If validation fails, we fall back to line-level merge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No cross-file awareness.&lt;/strong&gt; The merge happens file by file. If Branch A renames a function and Branch B adds a call to the old name, entity-level merge won't catch that. This is the same limitation Git has, just at a different granularity.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;I built this as &lt;a href="https://github.com/ataraxy-labs/weave" rel="noopener noreferrer"&gt;https://github.com/ataraxy-labs/weave&lt;/a&gt;, a Rust merge driver that plugs into Git. MIT licensed.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ataraxy-labs/tap/weave
&lt;span class="nb"&gt;cd &lt;/span&gt;your-repo &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; weave setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After that, Git uses it transparently. If you want to preview what it would do: &lt;code&gt;weave preview feature-branch&lt;/code&gt; does a dry run.&lt;/p&gt;

&lt;p&gt;You can also run the benchmark yourself:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;weave bench
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I'd love to hear about edge cases that this approach would struggle with. The entity-matching heuristic works well for named entities but has trouble with anonymous closures or unnamed blocks. What scenarios would you throw at it?&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Ataraxy-Labs" rel="noopener noreferrer"&gt;
        Ataraxy-Labs
      &lt;/a&gt; / &lt;a href="https://github.com/Ataraxy-Labs/weave" rel="noopener noreferrer"&gt;
        weave
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Entity-level semantic merge driver for Git. Resolves conflicts that git can't by understanding code structure via tree-sitter. 31/31 clean merges vs git's 15/31.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/Ataraxy-Labs/weave/assets/banner.svg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FAtaraxy-Labs%2Fweave%2Fassets%2Fbanner.svg" alt="weave" width="600"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
  Resolves merge conflicts that Git can't by understanding code structure via tree-sitter
&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://github.com/Ataraxy-Labs/weave/releases/latest" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/15c8f4da90200772e9faba0d53b39df5df381535452f216e55f35ce16edcaecd/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f417461726178792d4c6162732f77656176653f636f6c6f723d626c7565266c6162656c3d72656c65617365" alt="Release"&gt;&lt;/a&gt;
  &lt;a href="https://formulae.brew.sh/formula/weave" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/95c6c1b103972cbe03045d529db521fbf7607695c8909815936c3fb97db51268/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f686f6d65627265772d77656176652d6f72616e6765" alt="Homebrew"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/b626e173c5edefe432ae8fa0c1ebd27a25ab743455f72cfb65b8c9f1220959ad/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f727573742d737461626c652d6f72616e6765"&gt;&lt;img src="https://camo.githubusercontent.com/b626e173c5edefe432ae8fa0c1ebd27a25ab743455f72cfb65b8c9f1220959ad/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f727573742d737461626c652d6f72616e6765" alt="Rust"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/0d0331a01c91a2bf674eb0c7b762d698303535f24a36895a744ee2ddbbb314c7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d3132345f70617373696e672d627269676874677265656e"&gt;&lt;img src="https://camo.githubusercontent.com/0d0331a01c91a2bf674eb0c7b762d698303535f24a36895a744ee2ddbbb314c7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d3132345f70617373696e672d627269676874677265656e" alt="Tests"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/ca0c6858b2b7d15cf7bb028bc01735e0d760f7935e2cf4b9b411e3cabc70da75/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d302e322e362d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/ca0c6858b2b7d15cf7bb028bc01735e0d760f7935e2cf4b9b411e3cabc70da75/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d302e322e362d626c7565" alt="Version"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/Ataraxy-Labs/weave/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3921b6a636b7d3fc4db90f929b9047fb47fa4fdd6233a2b5fa60b827480fc2c5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d79656c6c6f77" alt="License"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/9015123903b9c598c76dc9597bda75aceecf64cf8cfbaf183a8affe66697f790/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c616e6775616765732d32312d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/9015123903b9c598c76dc9597bda75aceecf64cf8cfbaf183a8affe66697f790/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c616e6775616765732d32312d626c7565" alt="Languages"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The Problem&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Git merges by comparing &lt;strong&gt;lines&lt;/strong&gt;. When two branches both add code to the same file — even to completely different functions — Git sees overlapping line ranges and declares a conflict:&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
export function validateToken(token: string): boolean {
    return token.length &amp;gt; 0 &amp;amp;&amp;amp; token.startsWith("sk-");
}
=======
export function formatDate(date: Date): string {
    return date.toISOString().split('T')[0];
}
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; feature-branch
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These are &lt;strong&gt;completely independent changes&lt;/strong&gt;. There's no real conflict. But someone has to manually resolve it anyway.&lt;/p&gt;
&lt;p&gt;This happens constantly when multiple AI agents work on the same codebase. Agent A adds a function, Agent B adds a different function to the same file, and Git halts everything for a human to intervene.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How Weave Fixes This&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Weave replaces Git's line-based merge with &lt;strong&gt;entity-level merge&lt;/strong&gt;. Instead of diffing lines, it:&lt;/p&gt;


&lt;ol&gt;

&lt;li&gt;Parses all three versions (base…&lt;/li&gt;

&lt;/ol&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Ataraxy-Labs/weave" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





</description>
      <category>git</category>
      <category>ai</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
