use crate::packroot::pack_root;
use crate::types::{DeltaPayload, Edge, Symbol};
use crate::{kind_abbrev, kind_expand};
use std::collections::HashMap;
use std::fmt::Write;
pub fn encode_delta(d: &DeltaPayload) -> String {
let mut b = String::new();
let savings = if d.full_tokens > 0 {
100.0 * (1.0 - d.delta_tokens as f64 / d.full_tokens as f64)
} else {
0.0
};
writeln!(
b,
"GCF profile=graph tool={} delta=true base_root={} new_root={} tokens={} savings={:.0}%",
d.tool, d.base_root, d.new_root, d.delta_tokens, savings
)
.unwrap();
if !d.removed.is_empty() {
b.push_str("## removed\n");
for s in &d.removed {
let kind = kind_abbrev(&s.kind);
writeln!(b, "{} {}", kind, s.qualified_name).unwrap();
}
}
if !d.added.is_empty() {
b.push_str("## added\n");
for (i, s) in d.added.iter().enumerate() {
let kind = kind_abbrev(&s.kind);
writeln!(
b,
"@{} {} {} {:.2} {} {}",
i, kind, s.qualified_name, s.score, s.provenance, s.distance
)
.unwrap();
}
}
if !d.removed_edges.is_empty() {
b.push_str("## edges_removed\n");
for e in &d.removed_edges {
writeln!(b, "{} -> {} {}", e.source, e.target, e.edge_type).unwrap();
}
}
if !d.added_edges.is_empty() {
b.push_str("## edges_added\n");
for e in &d.added_edges {
writeln!(b, "{} -> {} {}", e.source, e.target, e.edge_type).unwrap();
}
}
b
}
fn parse_delta_edge(line: &str) -> Result<Edge, String> {
let idx = match line.find(" -> ") {
Some(i) => i,
None => {
return Err(format!(
"malformed_delta: edge line missing ' -> ': {:?}",
line
))
}
};
let source = &line[..idx];
let rest: Vec<&str> = line[idx + 4..].split_whitespace().collect();
if rest.len() != 2 {
return Err(format!(
"malformed_delta: edge line {:?} must be 'source -> target type'",
line
));
}
Ok(Edge {
source: source.to_string(),
target: rest[0].to_string(),
edge_type: rest[1].to_string(),
status: String::new(),
})
}
pub fn decode_delta(input: &str) -> Result<DeltaPayload, String> {
let trimmed = input.trim_end_matches('\n');
let lines: Vec<&str> = trimmed.split('\n').collect();
if lines.is_empty() || lines[0].is_empty() {
return Err("missing_header: empty delta payload".to_string());
}
let header = lines[0].trim_end_matches('\r');
if !header.starts_with("GCF profile=graph") {
return Err(
"missing_profile: delta header must begin with 'GCF profile=graph'".to_string(),
);
}
let mut d = DeltaPayload {
tool: String::new(),
base_root: String::new(),
new_root: String::new(),
removed: Vec::new(),
added: Vec::new(),
removed_edges: Vec::new(),
added_edges: Vec::new(),
delta_tokens: 0,
full_tokens: 0,
};
for field in header.split_whitespace() {
if let Some((k, v)) = field.split_once('=') {
match k {
"tool" => d.tool = v.to_string(),
"base_root" => d.base_root = v.to_string(),
"new_root" => d.new_root = v.to_string(),
_ => {}
}
}
}
let mut section = "";
for raw in &lines[1..] {
let line = raw.trim_end_matches('\r');
if line.is_empty() {
continue;
}
if let Some(rest) = line.strip_prefix("## ") {
section = rest.trim();
match section {
"removed" | "added" | "edges_removed" | "edges_added" => {}
other => {
return Err(format!("malformed_delta: unknown section {:?}", other));
}
}
continue;
}
match section {
"removed" => {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() != 2 {
return Err(format!(
"malformed_delta: removed line {:?} must be 'kind qname'",
line
));
}
d.removed.push(Symbol {
kind: kind_expand(parts[0]),
qualified_name: parts[1].to_string(),
score: 0.0,
provenance: String::new(),
distance: 0,
signature: String::new(),
components: Default::default(),
});
}
"added" => {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() != 6 {
return Err(format!(
"malformed_delta: added line {:?} must be '@id kind qname score provenance distance'",
line
));
}
let score: f64 = parts[3]
.parse()
.map_err(|_| format!("malformed_delta: invalid added score {:?}", parts[3]))?;
let dist: i32 = parts[5].parse().map_err(|_| {
format!("malformed_delta: invalid added distance {:?}", parts[5])
})?;
d.added.push(Symbol {
kind: kind_expand(parts[1]),
qualified_name: parts[2].to_string(),
score,
provenance: parts[4].to_string(),
distance: dist,
signature: String::new(),
components: Default::default(),
});
}
"edges_removed" => {
d.removed_edges.push(parse_delta_edge(line)?);
}
"edges_added" => {
d.added_edges.push(parse_delta_edge(line)?);
}
_ => {
return Err(format!(
"malformed_delta: data line {:?} before any section header",
line
));
}
}
}
Ok(d)
}
#[allow(clippy::too_many_arguments)]
pub fn verify_delta(
base_symbols: &[Symbol],
base_edges: &[Edge],
removed_symbols: &[Symbol],
added_symbols: &[Symbol],
removed_edges: &[Edge],
added_edges: &[Edge],
expected_new_root: &str,
) -> Result<(Vec<Symbol>, Vec<Edge>), String> {
let mut sym_map: HashMap<(String, String), Symbol> = HashMap::with_capacity(base_symbols.len());
for s in base_symbols {
sym_map.insert((s.kind.clone(), s.qualified_name.clone()), s.clone());
}
for s in removed_symbols {
let key = (s.kind.clone(), s.qualified_name.clone());
if sym_map.remove(&key).is_none() {
return Err(format!(
"delta_invalid: removing symbol {} {} that does not exist in base",
s.kind, s.qualified_name
));
}
}
for s in added_symbols {
let key = (s.kind.clone(), s.qualified_name.clone());
if sym_map.contains_key(&key) {
return Err(format!(
"delta_invalid: adding symbol {} {} that already exists",
s.kind, s.qualified_name
));
}
sym_map.insert(key, s.clone());
}
let result_symbols: Vec<Symbol> = sym_map.into_values().collect();
let mut edge_map: HashMap<(String, String, String), Edge> =
HashMap::with_capacity(base_edges.len());
for e in base_edges {
edge_map.insert(
(e.source.clone(), e.target.clone(), e.edge_type.clone()),
e.clone(),
);
}
for e in removed_edges {
let key = (e.source.clone(), e.target.clone(), e.edge_type.clone());
if edge_map.remove(&key).is_none() {
return Err(format!(
"delta_invalid: removing edge {} -> {} {} that does not exist",
e.source, e.target, e.edge_type
));
}
}
for e in added_edges {
let key = (e.source.clone(), e.target.clone(), e.edge_type.clone());
if edge_map.contains_key(&key) {
return Err(format!(
"delta_invalid: adding edge {} -> {} {} that already exists",
e.source, e.target, e.edge_type
));
}
edge_map.insert(key, e.clone());
}
let result_edges: Vec<Edge> = edge_map.into_values().collect();
let computed_root = pack_root(&result_symbols, &result_edges);
if computed_root != expected_new_root {
return Err(format!(
"root_mismatch: computed {}, expected {}",
computed_root, expected_new_root
));
}
Ok((result_symbols, result_edges))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Edge, Symbol};
#[test]
fn test_encode_delta() {
let d = DeltaPayload {
tool: "context_for_task".to_string(),
base_root: "aaa111".to_string(),
new_root: "bbb222".to_string(),
removed: vec![Symbol {
qualified_name: "pkg.OldFunc".to_string(),
kind: "function".to_string(),
score: 0.0,
provenance: String::new(),
distance: 0,
signature: String::new(),
components: Default::default(),
}],
added: vec![Symbol {
qualified_name: "pkg.NewFunc".to_string(),
kind: "function".to_string(),
score: 0.85,
provenance: "rwr".to_string(),
distance: 0,
signature: String::new(),
components: Default::default(),
}],
removed_edges: vec![Edge {
source: "a".to_string(),
target: "b".to_string(),
edge_type: "calls".to_string(),
status: String::new(),
}],
added_edges: vec![Edge {
source: "c".to_string(),
target: "d".to_string(),
edge_type: "uses".to_string(),
status: String::new(),
}],
delta_tokens: 30,
full_tokens: 200,
};
let output = encode_delta(&d);
assert!(output.contains("delta=true"));
assert!(output.contains("base_root=aaa111"));
assert!(output.contains("new_root=bbb222"));
assert!(output.contains("tokens=30"));
assert!(output.contains("savings=85%"));
assert!(output.contains("## removed"));
assert!(output.contains("fn pkg.OldFunc"));
assert!(output.contains("## added"));
assert!(output.contains("@0 fn pkg.NewFunc 0.85 rwr 0"));
assert!(output.contains("## edges_removed"));
assert!(output.contains("a -> b calls"));
assert!(output.contains("## edges_added"));
assert!(output.contains("c -> d uses"));
}
#[test]
fn test_delta_savings_zero_full() {
let d = DeltaPayload {
tool: "t".to_string(),
base_root: "a".to_string(),
new_root: "b".to_string(),
removed: vec![],
added: vec![],
removed_edges: vec![],
added_edges: vec![],
delta_tokens: 0,
full_tokens: 0,
};
let output = encode_delta(&d);
assert!(output.contains("savings=0%"));
}
}