use crate::domain::{ContextBundle, TargetTool};
pub fn render(bundle: &ContextBundle, max_chars: usize) -> String {
let mut output = String::new();
output.push_str(match bundle.input.target {
TargetTool::Claude => "以下是给 Claude 使用的精简上下文。\n\n",
TargetTool::Codex => "以下是给 Codex 使用的精简上下文。\n\n",
TargetTool::Opencode => "以下是给 OpenCode 使用的精简上下文。\n\n",
});
if let Some(project) = &bundle.route.project {
output.push_str(&format!("项目:{} ({})\n", project.name, project.id));
output.push_str(&format!("项目命中原因:{}\n\n", project.reason));
}
if !bundle.route.modules.is_empty() {
output.push_str("模块:\n");
for module in &bundle.route.modules {
output.push_str(&format!("- {}\n", module.id));
}
output.push('\n');
}
if !bundle.route.scenes.is_empty() {
output.push_str("场景:\n");
for scene in &bundle.route.scenes {
output.push_str(&format!("- {}\n", scene.id));
}
output.push('\n');
}
if !bundle.route.lifecycle_candidates.is_empty() {
output.push_str("记忆(accepted / canonical):\n");
for candidate in &bundle.route.lifecycle_candidates {
let summary = truncate_chars(&candidate.summary, 120);
output.push_str(&format!(
"- [{}] [{}] {} — {}\n",
candidate.score, candidate.memory_type, candidate.title, summary
));
}
output.push('\n');
}
output.push_str("候选笔记:\n");
for candidate in &bundle.route.candidates {
output.push_str(&format!(
"- [{}] {}\n {}\n",
candidate.score, candidate.relative_path, candidate.excerpt
));
}
if output.chars().count() > max_chars {
output.chars().take(max_chars).collect()
} else {
output
}
}
fn truncate_chars(value: &str, max_chars: usize) -> String {
let chars: Vec<char> = value.chars().collect();
if chars.len() <= max_chars {
return value.to_string();
}
let mut out: String = chars.iter().take(max_chars).collect();
out.push('…');
out
}