use sqlx::SqlitePool;
use crate::cloud::observations::ObservationEvent;
use crate::context::retrieval::ScoredRuleChunk;
use crate::context::rule_source::RuleExample;
use super::trust_proof::{RuleTrustMap, format_trust_evidence};
pub(crate) struct RuleBlockArgs<'a> {
pub position: usize,
pub rel: f64,
pub rule: &'a ScoredRuleChunk,
pub trust_evidence: &'a RuleTrustMap,
pub examples: Option<&'a Vec<RuleExample>>,
pub example_bad_label: &'a str,
pub example_good_label: &'a str,
}
pub(crate) fn render_rule_block(args: &RuleBlockArgs<'_>) -> String {
let &RuleBlockArgs {
position,
rel,
rule,
trust_evidence,
examples,
example_bad_label,
example_good_label,
} = args;
let title = rule
.content
.lines()
.find_map(|l| l.strip_prefix("Rule Name: "))
.map(str::trim)
.filter(|s| !s.is_empty())
.unwrap_or("(untitled)");
let source = rule
.content
.lines()
.find_map(|l| l.strip_prefix("Source: "))
.map(str::trim)
.filter(|s| !s.is_empty());
let source_seg = source
.map(|s| format!(" \u{2190} learned from {s}"))
.unwrap_or_default();
let mut text = format!(
"## Memory {}: {}{} (rank score: {:.2} · raw: {:.3})\n\n",
position, title, source_seg, rel, rule.score
);
if let Some(proof) = trust_evidence.get(&rule.skill_id)
&& let Some(label) = format_trust_evidence(proof)
{
text.push_str(&format!("Proof: {label}\n\n"));
}
text.push_str(&rule.content);
if let Some(examples) = examples
&& !examples.is_empty()
{
text.push_str("\n\n### Examples\n");
for ex in examples {
text.push_str(&format!(
"\n{}\n```\n{}\n```\n\n{}\n```\n{}\n```\n",
example_bad_label, ex.bad_code, example_good_label, ex.good_code
));
if let Some(desc) = &ex.description
&& !desc.is_empty()
{
text.push_str(&format!("\n{desc}\n"));
}
}
}
text.push_str("\n---\n\n");
text
}
pub(crate) struct RuleServe<'a> {
pub tool: &'a str,
pub session_id: Option<&'a str>,
pub event_session_id: &'a str,
pub repo_full_name: Option<&'a str>,
pub target_file: Option<&'a str>,
pub query: &'a str,
pub rule_ids: &'a [String],
pub top_k: i64,
pub strict_match_count: i64,
pub estimated_tokens: i64,
}
pub(crate) async fn serve_and_record(
db: &SqlitePool,
serve: RuleServe<'_>,
record_err_prefix: Option<&str>,
) -> ObservationEvent {
let record_result = crate::mcp_rule_serves::record(
db,
&crate::mcp_rule_serves::McpRuleServeInput {
tool: serve.tool,
session_id: serve.session_id,
repo_full_name: serve.repo_full_name,
file_path: serve.target_file,
query_text: serve.query,
rule_ids: serve.rule_ids,
top_k: serve.top_k,
strict_match_count: serve.strict_match_count,
estimated_tokens: serve.estimated_tokens,
},
)
.await;
if let (Err(e), Some(prefix)) = (record_result, record_err_prefix) {
eprintln!("{prefix}: {e}");
}
ObservationEvent::McpRuleServed {
tool: serve.tool.to_owned(),
session_id: serve.event_session_id.to_owned(),
repo_full_name: serve.repo_full_name.map(ToOwned::to_owned),
file_path: serve.target_file.map(ToOwned::to_owned),
query_hash: crate::mcp_rule_serves::query_hash(serve.query),
rule_ids: serve.rule_ids.to_vec(),
top_k: serve.top_k,
was_empty: serve.rule_ids.is_empty(),
strict_match_count: serve.strict_match_count,
estimated_tokens: serve.estimated_tokens,
served_at: chrono::Utc::now(),
}
}