Every commit, the technical decisions behind it, and what we learned building weave.
Structured post-merge output for agents. Three connected improvements:
weave summary CLI command: reads a file with weave conflict markers, parses them into structured data, and outputs a summary with entity names, complexity classifications, confidence levels, and hints. Supports --json for machine-readable output.weave_merge_summary MCP tool: 15th MCP tool. Returns the same structured JSON as weave summary --json, letting agents query merge results from their MCP session without regex parsing.parse_weave_conflicts(): new public function in weave-core that parses weave-enhanced conflict markers back into structured ParsedConflict data.Also fixed the website: updated MCP tool count from 9 to 15 and added the 6 missing tools to the grid.
Agents need structured post-merge output, not just conflict markers. A recurring theme from GitHub discussions (claude-code, gitbutler, goose, aider): agents can act on conflicts much faster when they get machine-readable summaries with classification, confidence, and actionable hints. The ConGra taxonomy maps directly to resolution strategies: Text conflicts are auto-resolvable, Syntax conflicts need caller checks, Functional conflicts need intent understanding.
Three new features, expanded benchmark to 31 scenarios, and first public release:
/** */, Rust ///, JavaDoc) are now bundled with their associated entity during region extraction. Comments travel with their function during merge, and region content comparison supplements structural hash to detect comment-only changes.extract_container_wrapper and extract_member_chunks now handle indentation-based containers (class Foo:) alongside brace-delimited ones. Both adding methods to a Python class auto-resolves.Benchmark: weave 31/31 (100%) vs git 15/31 (48%). 7 new scenarios: method reorder, Python class methods, Rust impl methods, enum modify+add, JSDoc+body, Rust doc comments, Go functions.
v0.1.0 released on Homebrew: brew install ataraxy-labs/tap/weave
Three major improvements:
@cache + @deprecated), weave merges them cleanly. Works for Python decorators, Java annotations, and TypeScript decorators — at both entity and inner-entity (class member) levels.@-prefixed decorators bundle with their member in inner merge. Single-member containers now go through inner merge (needed for decorator merge on single-method classes).Benchmark: weave 24/24 (100%) vs git 11/24 (46%). 8 new scenarios: Python decorators, decorator+body changes, TS class decorators, TS interface fields, Rust enum variants, Java methods, Java annotations, C functions. All resolve cleanly.
Decorators and annotations are semantically unordered, like imports — they can be merged commutatively. The split_decorators() approach cleanly separates decorator concerns from body concerns, allowing independent merge strategies. Combined with the inner entity merge for class members, this handles the real-world pattern where one agent adds @Injectable while another adds @Deprecated to the same method.
Major expansion of the benchmark suite from 7 to 16 scenarios. Results in release mode:
New features:
very_high (one side changed), high (diffy 3-way), medium (inner merge/fallback), conflictNew benchmark scenarios: both add functions at end, both add methods to class, Rust/Python import merging, insert-between-existing, reformat-vs-modify, both add exports
Most "conflicts" in multi-agent workflows are false positives — git reports conflicts because changes are on adjacent lines, not because they're actually incompatible. Entity-level understanding eliminates this entire class of problems. Research shows 10-20% of all git merges produce conflicts (Ghiotto et al. 2019), and our whitespace-aware shortcut handles the common case where one agent's formatter runs while another makes real changes.
New weave bench command runs 7 merge scenarios comparing weave's entity-level merge against git's line-level merge (via diffy). Results in release mode:
Bug fixed: sem-core was extracting local variables inside class methods (const user) as top-level entities. When two agents' methods both declared const user, this produced a false BothAdded conflict. Added filter_nested_entities() — strips entities whose line range is fully contained within another entity.
When composing AST-level tools, watch for over-extraction: parsers that extract variables/assignments from inside functions produce nested entities that create false conflicts during merge. The fix is simple — O(n²) containment check on line ranges — but the symptom is subtle: a class merge that works with 3 methods but fails with 4 because the 4th method introduces a variable name collision.
Inspired by RefFilter and IntelliMerge: when one branch renames an entity and the other modifies a different entity, detect the rename via sem-core's AST-normalized structural_hash and merge cleanly. Previously this produced false delete+add conflicts.
Builds a rename map (new_id → base_id) before the main merge loop. The merge treats renamed entities as the same entity with a name change — modifications from the other branch apply correctly.
structural_hash (AST-normalized, strips comments + whitespace) for matchingRename detection in merge is a graph matching problem. structural_hash gives us content-based matching for free — if two entities have the same normalized AST structure but different IDs (names), one was likely renamed. The key guard: only match if the old ID doesn't exist in the branch (otherwise it's a copy, not a rename).
Implements the key insight from LastMerge (arXiv:2507.19687): class members are unordered children. When diffy fails on a container entity (class, impl, trait), chunk the body into method-level blocks, match by name, and merge each independently.
The core multi-agent scenario this resolves: two agents modify different methods in the same class. Git produces a conflict because the changes are in adjacent hunks. Weave now resolves this cleanly.
extract_member_chunks(): splits class body by indentation level, names each chunk by method/field signaturetry_inner_entity_merge(): match-by-name three-way merge on member chunkssem-core extracts classes as single entities (methods are NOT separate — method_definition isn't in TypeScript's entity_node_types). This means class merges needed a secondary decomposition layer. The indent-based chunking is language-agnostic and works for TS classes, Python classes, Rust impls, and Go structs without extra parser work.
Implements the ConGra taxonomy from arXiv:2409.14121: classifies conflicts as Text (T), Syntax (S), Functional (F), or composites (T+S, T+F, S+F, T+S+F).
The classification appears on every EntityConflict, helping agents choose resolution strategies: text-only conflicts are trivially auto-resolvable, syntax conflicts may need type-checking, functional conflicts need careful review.
classify_change(): compares two versions, detects text (whitespace/comment), syntax (signature), functional (body) changesclassify_conflict(): combines ours+theirs classifications into composite typeWhen an agent claims an entity, the tool now checks the entity dependency graph for related entities claimed by other agents. Returns warnings like: "processData depends on validateInput which is claimed by agent-2".
This predicts merge conflicts before they happen — the first step from reactive conflict resolution to proactive conflict prevention.
New tool validates a merge for semantic risks. Finds entities modified in both branches, then uses the entity dependency graph to detect cross-references between them.
Returns warnings like: "processData was modified and references validateInput which was also modified" — cases where the merge is syntactically clean but semantically risky. Weave MCP server now exposes 13 tools total.
Based on Sesame (arXiv:2407.18888) which achieved 91% spurious conflict reduction. When entity-level merge isn't possible, the line-level fallback now expands syntactic separators ({ } ;) onto separate lines before merging. This gives diffy finer-grained alignment across block boundaries.
String contents are preserved during expansion. Falls back to plain merge if separator-expanded merge still conflicts (for cleaner markers).
Sesame's insight: line-based merge tools fail when syntactic boundaries don't align with line boundaries. Expanding { and } onto their own lines is a zero-cost preprocessing step that dramatically improves diffy's alignment. Works as a graceful degradation path when AST parsing isn't available.
New validate module detects when auto-merged entities reference other entities that were also modified — a semantic risk even when the merge is syntactically clean. Uses sem-core's entity dependency graph.
Two warning types: DependencyAlsoModified (you call something that changed) and DependentAlsoModified (something that calls you changed). MergeResult now carries a warnings field alongside conflicts.
Three new tools expose sem-core's cross-file entity dependency graph:
weave_get_dependencies: what does this entity call/reference?weave_get_dependents: who calls/references this entity?weave_impact_analysis: transitive blast radius if an entity changesBuilds the full repo graph on each call using sem-core's two-pass extraction (entities → symbol table → reference edges).
Inspired by Mergiraf: treats import-heavy regions as unordered sets instead of ordered sequences. Eliminates the most common false conflict in multi-agent workflows (both agents adding different imports).
Algorithm: compute set differences from base for each branch, merge additions, remove deletions, deduplicate. Supports JS/TS import, Rust use, Python import, C/C++ #include, and require().
MCP server failed when launched from outside a git repo. Now uses 3-strategy discovery:
git -C <parent> rev-parse --show-toplevelWEAVE_REPO env varConverts absolute paths to repo-relative internally so tools work with both.
Each entry includes commit context, what changed, what broke, and a "lesson learned" box. Covers: lazy MCP init, automerge Transactable, rmcp schemars, cargo install syntax, entity.content vs raw regions, diffy behavior, and Claude Code MCP scoping.
The MCP server was crashing on startup when launched from outside a git repository. Claude Code starts MCP servers from the user's home directory, which usually isn't a git repo.
The fix: Replaced eager find_repo_root() in main() with lazy initialization via tokio::sync::MappedMutexGuard. The server now starts instantly and only discovers the repo root when a tool is actually called.
Arc<Mutex<Option<RepoContext>>> for lazy init patternMutexGuard::map() projects through the Option to return a mapped guardMCP servers must start without assumptions about the working directory. Lazy initialization is critical — defer I/O and environment discovery to the first actual tool call, not server startup. MutexGuard::map(guard, |opt| opt.as_mut().unwrap()) is the idiomatic Rust pattern for projecting a mutex guard through a wrapper type.
Documentation only showed Claude Desktop setup (JSON config file). Added the claude mcp add command for Claude Code CLI users.
Key detail: --scope user is required to make the MCP server available across all projects. Without it, the server is scoped to the current project directory and won't appear in /mcp when running Claude Code elsewhere.
Claude Code has two MCP scopes: project (default, stored in project .claude.json) and user (global, stored in ~/.claude.json). For tools that should work across repos, always use --scope user.
Broke the monolithic 799-line index.html into three focused pages:
Extracted shared CSS into style.css with article-specific styles for the learn page (blog-style layout with highlight boxes and code examples).
All 5 Cargo.toml files referenced sem-core via local path (path = "../../../sem/crates/sem-core"). This works for local development but breaks cargo install --git because the relative path doesn't exist on the user's machine.
Fix: Changed all references to sem-core = { git = "https://github.com/Ataraxy-Labs/sem", version = "0.2" }. Cargo resolves the crate from the git repo's workspace automatically.
When publishing a crate that depends on another workspace crate from a different repo, use git = "..." dependencies, not path = "...". Cargo resolves workspace members from git URLs automatically — just point to the repo root and Cargo finds the crate by name.
Documentation had cargo install --git <url> --path crates/weave-mcp which doesn't work — --git and --path are mutually exclusive flags in Cargo.
Correct syntax: cargo install --git <url> <crate-name>. For a workspace repo, pass the binary crate name as a positional argument. Cargo looks through all workspace members to find the matching [package] name.
cargo install --git takes the crate name as a positional arg, not via --path. The correct form: cargo install --git https://github.com/org/repo crate-name. Cargo clones the repo, finds the crate in the workspace, and builds + installs it.
The largest commit — two new crates adding agent coordination to weave.
weave-crdt: Automerge 0.5 backed entity state. Advisory claims, agent registration, heartbeat-based liveness, stale cleanup, and conflict detection. 20 tests covering all operations.
weave-mcp: MCP server with 9 tools over stdio transport using rmcp 0.14. Wraps all CRDT operations + entity extraction + merge preview into callable agent tools.
Three hard bugs hit during implementation:
put(), put_object(), delete(), insert() methods on AutoCommit require use automerge::transaction::Transactable; in scope. Without it, the compiler says the methods don't exist — misleading since AutoCommit clearly implements them.#[derive(rmcp::schemars::JsonSchema)] doesn't work because derive macros resolve at compile time against the local crate namespace. Fix: add schemars = "1" as a direct dependency and use #[derive(schemars::JsonSchema)] instead.server.serve(tokio::io::stdin()) fails — stdio transport requires both directions: server.serve((tokio::io::stdin(), tokio::io::stdout())).Automerge 0.5: Always import use automerge::transaction::Transactable; when using AutoCommit. The trait provides all mutation methods. rmcp 0.14: Derive macros from re-exported crates (rmcp::schemars) don't work — add the crate as a direct dep. Stdio MCP transport needs a (stdin, stdout) tuple, not just stdin.
Single-page documentation site with dark monospace aesthetic, inspired by the sem docs. Covered all three phases: merge driver algorithm, CRDT state model, and MCP server tools. Later split into multiple pages in a5d130d.
The foundation — a Cargo workspace with three crates implementing entity-level semantic merge for Git.
weave-core (the algorithm):
Entity and Interstitial regionsdiffyweave-driver (git integration):
%O %A %B %L %P argumentsentity_merge(), writes result to %Aweave-cli (user interface):
weave setup — configures .gitattributes and git merge driverweave preview <branch> — dry-run merge analysisKey design decision: sem-core's entity.content strips syntax like export, so we use region content (raw file lines by byte range) for merge output instead of entity content. This preserves the original source exactly.
sem-core entities vs regions: entity.content is the parsed/normalized body (strips keywords like export). For merge output, always use the raw file content by line range to preserve original syntax exactly. diffy: diffy::merge() works best for non-adjacent changes; overlapping changes within the same entity body still produce conflict markers.