use std::path::{Path, PathBuf};
use crate::core::knowledge::{KnowledgeFact, ProjectKnowledge};
pub fn run(args: &[String]) {
let format = args
.iter()
.position(|a| a == "--format")
.and_then(|i| args.get(i + 1))
.map_or("mdc", |s| s.as_str());
let root_arg = args
.iter()
.position(|a| a == "--root")
.and_then(|i| args.get(i + 1))
.map_or(".", |s| s.as_str());
let project_root = std::fs::canonicalize(root_arg).unwrap_or_else(|_| PathBuf::from(root_arg));
let project_root_str = project_root.to_string_lossy();
let Some(knowledge) = ProjectKnowledge::load(&project_root_str) else {
eprintln!("No knowledge found for project: {project_root_str}");
eprintln!("Run `lean-ctx` in a session first to accumulate knowledge.");
std::process::exit(1);
};
let high_confidence: Vec<&KnowledgeFact> = knowledge
.facts
.iter()
.filter(|f| f.confidence >= 0.6 && f.retrieval_count >= 2)
.collect();
if high_confidence.is_empty() {
eprintln!("No high-confidence knowledge facts found (need confidence >= 0.6 and at least 2 retrievals).");
std::process::exit(0);
}
match format {
"mdc" => export_mdc(&high_confidence, &project_root),
"agents-md" => export_agents_md(&high_confidence, &project_root),
"claude-md" => export_claude_md(&high_confidence, &project_root),
_ => {
eprintln!("Unknown format: {format}. Supported: mdc, agents-md, claude-md");
std::process::exit(1);
}
}
}
fn export_mdc(facts: &[&KnowledgeFact], project_root: &Path) {
let output_path = project_root.join(".cursor/rules/lean-ctx-knowledge.mdc");
let content = build_mdc_content(facts);
if let Some(parent) = output_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
match std::fs::write(&output_path, &content) {
Ok(()) => {
println!(
"Exported {} rules to {}",
facts.len(),
output_path.display()
);
}
Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
}
}
fn export_agents_md(facts: &[&KnowledgeFact], project_root: &Path) {
let output_path = project_root.join("AGENTS.md");
let section = build_agents_section(facts);
if output_path.exists() {
let existing = std::fs::read_to_string(&output_path).unwrap_or_default();
let marker_start = "<!-- lean-ctx-knowledge-start -->";
let marker_end = "<!-- lean-ctx-knowledge-end -->";
let new_content = if existing.contains(marker_start) {
let before = existing.split(marker_start).next().unwrap_or("");
let after = existing.split(marker_end).nth(1).unwrap_or("");
format!("{before}{marker_start}\n{section}\n{marker_end}{after}")
} else {
format!("{existing}\n\n{marker_start}\n{section}\n{marker_end}\n")
};
match std::fs::write(&output_path, &new_content) {
Ok(()) => println!("Updated {} rules in {}", facts.len(), output_path.display()),
Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
}
} else {
let content = format!(
"# Project Knowledge (auto-generated by lean-ctx)\n\n\
<!-- lean-ctx-knowledge-start -->\n{section}\n<!-- lean-ctx-knowledge-end -->\n"
);
match std::fs::write(&output_path, &content) {
Ok(()) => println!(
"Created {} with {} rules",
output_path.display(),
facts.len()
),
Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
}
}
}
fn export_claude_md(facts: &[&KnowledgeFact], project_root: &Path) {
let output_path = project_root.join("CLAUDE.md");
let section = build_agents_section(facts);
if output_path.exists() {
let existing = std::fs::read_to_string(&output_path).unwrap_or_default();
let marker_start = "<!-- lean-ctx-knowledge-start -->";
let marker_end = "<!-- lean-ctx-knowledge-end -->";
let new_content = if existing.contains(marker_start) {
let before = existing.split(marker_start).next().unwrap_or("");
let after = existing.split(marker_end).nth(1).unwrap_or("");
format!("{before}{marker_start}\n{section}\n{marker_end}{after}")
} else {
format!("{existing}\n\n{marker_start}\n{section}\n{marker_end}\n")
};
match std::fs::write(&output_path, &new_content) {
Ok(()) => println!("Updated {} rules in {}", facts.len(), output_path.display()),
Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
}
} else {
let content = format!(
"# Project Rules (auto-generated by lean-ctx)\n\n\
<!-- lean-ctx-knowledge-start -->\n{section}\n<!-- lean-ctx-knowledge-end -->\n"
);
match std::fs::write(&output_path, &content) {
Ok(()) => println!(
"Created {} with {} rules",
output_path.display(),
facts.len()
),
Err(e) => eprintln!("Error writing {}: {e}", output_path.display()),
}
}
}
fn build_mdc_content(facts: &[&KnowledgeFact]) -> String {
let mut out = String::from(
"---\ndescription: \"Project knowledge (auto-generated by lean-ctx export-rules)\"\n\
globs: \"**/*\"\nalwaysApply: true\n---\n\n\
# Project Knowledge\n\n",
);
let mut by_category: std::collections::BTreeMap<&str, Vec<&&KnowledgeFact>> =
std::collections::BTreeMap::new();
for fact in facts {
by_category.entry(&fact.category).or_default().push(fact);
}
for (category, cat_facts) in &by_category {
out.push_str(&format!("## {category}\n\n"));
for fact in cat_facts {
out.push_str(&format!("- **{}**: {}\n", fact.key, fact.value));
}
out.push('\n');
}
out
}
fn build_agents_section(facts: &[&KnowledgeFact]) -> String {
let mut out = String::from("## Project Knowledge (lean-ctx)\n\n");
let mut by_category: std::collections::BTreeMap<&str, Vec<&&KnowledgeFact>> =
std::collections::BTreeMap::new();
for fact in facts {
by_category.entry(&fact.category).or_default().push(fact);
}
for (category, cat_facts) in &by_category {
out.push_str(&format!("### {category}\n\n"));
for fact in cat_facts {
out.push_str(&format!("- **{}**: {}\n", fact.key, fact.value));
}
out.push('\n');
}
out.trim_end().to_string()
}