diff --git a/.lattice/requirements/cli/027-lattice-diff---raw-outputs-actual-git-di.yaml b/.lattice/requirements/cli/027-lattice-diff---raw-outputs-actual-git-di.yaml new file mode 100644 index 0000000..27d7785 --- /dev/null +++ b/.lattice/requirements/cli/027-lattice-diff---raw-outputs-actual-git-di.yaml @@ -0,0 +1,18 @@ +id: REQ-CLI-027 +type: requirement +title: lattice diff --raw outputs actual git diff +body: lattice diff --raw should output the raw git diff of .lattice/ files since the base ref, suitable for piping into other tools or reviewing exact YAML changes. Output should be uncolored and machine-consumable. +status: active +version: 1.0.0 +created_at: 2026-03-15T21:35:43.916114+00:00 +created_by: agent:claude-2026-03-15 +requested_by: George Moon +priority: P2 +category: cli +edges: + derives_from: + - target: THX-AGENT-NATIVE-TOOLS + version: 1.0.0 + depends_on: + - target: REQ-CLI-006 + version: 1.0.0 diff --git a/src/diff.rs b/src/diff.rs index e89ad42..9ef5bf8 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -210,21 +210,24 @@ fn was_resolved(old_yaml: &str, new_node: &LatticeNode) -> bool { false } +/// Resolve the base git ref, falling back to merge-base with main/master. +fn resolve_base_ref(since: Option<&str>) -> Result { + match since { + Some(r) => Ok(r.to_string()), + None => git_merge_base("main") + .or_else(|_| git_merge_base("master")) + .map_err(|_| { + DiffError::GitError("could not find merge-base with main or master".to_string()) + }), + } +} + /// Compute lattice diff since a given git ref. /// /// If `since` is None, defaults to merge-base with `main`. pub fn lattice_diff(lattice_root: &Path, since: Option<&str>) -> Result { let lattice_dir = lattice_root.join(".lattice"); - - // Determine the base ref - let base_ref = match since { - Some(r) => r.to_string(), - None => git_merge_base("main") - .or_else(|_| git_merge_base("master")) - .map_err(|_| { - DiffError::GitError("could not find merge-base with main or master".to_string()) - })?, - }; + let base_ref = resolve_base_ref(since)?; let changes = git_diff_name_status(&base_ref, &lattice_dir)?; @@ -352,6 +355,31 @@ pub fn format_entry_text(entry: &DiffEntry) -> String { format_entry(entry) } +/// Produce a raw git diff of `.lattice/` files since a given ref. +pub fn git_diff_raw(lattice_root: &Path, since: Option<&str>) -> Result { + let lattice_dir = lattice_root.join(".lattice"); + let base_ref = resolve_base_ref(since)?; + + let output = Command::new("git") + .arg("diff") + .arg(&base_ref) + .arg("--") + .arg(&lattice_dir) + .output() + .map_err(|e| DiffError::GitError(format!("failed to run git diff: {}", e)))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(DiffError::GitError(format!( + "git diff failed: {}", + stderr.trim() + ))); + } + + Ok(String::from_utf8(output.stdout) + .unwrap_or_else(|e| String::from_utf8_lossy(&e.into_bytes()).into_owned())) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/main.rs b/src/main.rs index f817776..a5b64b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -422,6 +422,10 @@ enum Commands { #[arg(long)] md: bool, + /// Show raw git diff of .lattice/ files + #[arg(long)] + raw: bool, + /// Output format (text, json) #[arg(short, long, default_value = "text")] format: String, @@ -3319,9 +3323,28 @@ fn run_command(command: Commands) { } } - Commands::Diff { since, md, format } => { + Commands::Diff { + since, + md, + raw, + format, + } => { let root = get_lattice_root(); + if raw { + match lattice::diff::git_diff_raw(&root, since.as_deref()) { + Ok(output) => { + if output.is_empty() { + println!("No lattice changes detected."); + } else { + print!("{}", output); + } + } + Err(e) => emit_error(&format, "diff_error", &e.to_string()), + } + return; + } + match lattice_diff(&root, since.as_deref()) { Ok(result) => { if md {