diff --git a/crates/weave-cli/src/commands/apply.rs b/crates/weave-cli/src/commands/apply.rs new file mode 100644 index 00000000..39ce1f29 --- /dev/null +++ b/crates/weave-cli/src/commands/apply.rs @@ -0,0 +1,51 @@ +use colored::Colorize; +use weave_core::git::find_repo_root; +use weave_crdt::{reconstruct_file_from_crdt, EntityStateDoc}; + +/// Materialize entity edits from the CRDT back onto the working files. +/// +/// This is weave's write side: the CRDT is the authoritative source of an +/// entity's content, and this projects that content onto disk, reassembling +/// the file from its entities plus the interstitial text between them. It is +/// the counterpart to editing entities in the CRDT (via the MCP tools or +/// `weave claim` flows): edit entities, then `weave apply` to write the files. +pub fn run(files: &[String]) -> Result<(), Box> { + if files.is_empty() { + return Err("Specify one or more files to apply, e.g. `weave apply src/lib.rs`".into()); + } + + let repo_root = find_repo_root()?; + let state_path = repo_root.join(".weave").join("state.automerge"); + let state = EntityStateDoc::open(&state_path)?; + + let mut applied = 0usize; + for file in files { + let reconstructed = reconstruct_file_from_crdt(&state, file)?; + if reconstructed.is_empty() { + println!( + " {} {} — no entities tracked in the CRDT, skipping", + "!".yellow().bold(), + file + ); + continue; + } + + let target = repo_root.join(file); + let before = std::fs::read_to_string(&target).unwrap_or_default(); + if before == reconstructed { + println!(" {} {} — already up to date", "·".dimmed(), file); + continue; + } + + std::fs::write(&target, &reconstructed)?; + applied += 1; + println!(" {} {} — materialized from CRDT", "✓".green().bold(), file); + } + + println!( + "{} {} file(s) written from the CRDT.", + "weave apply:".bold(), + applied + ); + Ok(()) +} diff --git a/crates/weave-cli/src/commands/mod.rs b/crates/weave-cli/src/commands/mod.rs index c7001b1d..1db5211a 100644 --- a/crates/weave-cli/src/commands/mod.rs +++ b/crates/weave-cli/src/commands/mod.rs @@ -1,3 +1,4 @@ +pub mod apply; pub mod bench; pub mod bench_repo; pub mod claim; diff --git a/crates/weave-cli/src/main.rs b/crates/weave-cli/src/main.rs index a0b770ab..afda5a09 100644 --- a/crates/weave-cli/src/main.rs +++ b/crates/weave-cli/src/main.rs @@ -26,6 +26,11 @@ enum Commands { }, /// Remove weave merge driver from the current Git repo Unsetup, + /// Materialize entity edits from the CRDT back onto the working files + Apply { + /// One or more files to write from the CRDT + files: Vec, + }, /// Preview what a merge between branches would look like Preview { /// The branch to merge into HEAD @@ -99,6 +104,7 @@ fn main() { global, } => commands::setup::run(driver.as_deref(), local, global), Commands::Unsetup => commands::setup::unsetup(), + Commands::Apply { ref files } => commands::apply::run(files), Commands::Preview { ref branch, ref file,