use super::proof_counters::{
LOCAL_PROOF_WINDOW_DAYS, REVIEW_MINUTES_PER_ACCEPTED_PROOF, normalized_repo_aliases,
};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(in crate::commands::status) struct LocalHeroEvidence {
pub(in crate::commands::status) scope: String,
pub(in crate::commands::status) rule_id: String,
pub(in crate::commands::status) title: String,
pub(in crate::commands::status) source_repo: Option<String>,
pub(in crate::commands::status) target_repo_full_name: Option<String>,
pub(in crate::commands::status) target_pr_number: Option<i64>,
pub(in crate::commands::status) sample_file: Option<String>,
pub(in crate::commands::status) accepted_edits: i64,
pub(in crate::commands::status) signed_diff_proofs: i64,
pub(in crate::commands::status) recall_events: i64,
pub(in crate::commands::status) best_recall_rank: Option<i64>,
pub(in crate::commands::status) latest_recall_file: Option<String>,
pub(in crate::commands::status) agent_serves: i64,
pub(in crate::commands::status) strict_agent_serves: i64,
pub(in crate::commands::status) latest_agent_serve_file: Option<String>,
pub(in crate::commands::status) saved_review_minutes: i64,
pub(in crate::commands::status) latest_accepted_at: Option<String>,
}
#[derive(Debug, Clone, sqlx::FromRow)]
struct HeroCandidateRow {
rule_id: String,
title: String,
source_repo: Option<String>,
accepted_edits: i64,
signed_diff_proofs: i64,
latest_accepted_at: Option<String>,
}
#[derive(Debug, Clone, sqlx::FromRow)]
struct HeroAcceptedFixRow {
target_repo_full_name: Option<String>,
target_pr_number: Option<i64>,
sample_file: Option<String>,
}
pub(in crate::commands::status) async fn local_hero_evidence(
db: &difflore_core::SqlitePool,
repo_aliases: &[String],
) -> Option<LocalHeroEvidence> {
let normalized_aliases = normalized_repo_aliases(repo_aliases);
if normalized_aliases.is_empty() {
return None;
}
fetch_local_hero_evidence(db, Some(&normalized_aliases), "currentRepo").await
}
async fn fetch_local_hero_evidence(
db: &difflore_core::SqlitePool,
normalized_repos: Option<&[String]>,
scope: &str,
) -> Option<LocalHeroEvidence> {
let candidate = fetch_hero_candidate_row(db, normalized_repos).await?;
let latest_fix = fetch_hero_accepted_fix(db, &candidate.rule_id).await;
let recall_events = difflore_core::observability::rule_outcomes::recall_count_for(
db,
&candidate.rule_id,
LOCAL_PROOF_WINDOW_DAYS,
)
.await
.unwrap_or(0);
let top_recall = difflore_core::observability::rule_outcomes::latest_top3_recall_for(
db,
&candidate.rule_id,
LOCAL_PROOF_WINDOW_DAYS,
)
.await
.ok()
.flatten();
let mcp_summary = difflore_core::observability::mcp_rule_serves::summary_for_rule(
db,
&candidate.rule_id,
LOCAL_PROOF_WINDOW_DAYS,
)
.await
.unwrap_or_default();
Some(LocalHeroEvidence {
scope: scope.to_owned(),
rule_id: candidate.rule_id,
title: candidate.title,
source_repo: candidate.source_repo,
target_repo_full_name: latest_fix
.as_ref()
.and_then(|fix| fix.target_repo_full_name.clone()),
target_pr_number: latest_fix.as_ref().and_then(|fix| fix.target_pr_number),
sample_file: latest_fix.as_ref().and_then(|fix| fix.sample_file.clone()),
accepted_edits: candidate.accepted_edits,
signed_diff_proofs: candidate.signed_diff_proofs,
recall_events,
best_recall_rank: top_recall.as_ref().map(|recall| recall.rank),
latest_recall_file: top_recall.and_then(|recall| recall.file_path),
agent_serves: mcp_summary.calls,
strict_agent_serves: mcp_summary.strict_match_calls,
latest_agent_serve_file: mcp_summary.latest.and_then(|serve| serve.file_path),
saved_review_minutes: candidate.accepted_edits * REVIEW_MINUTES_PER_ACCEPTED_PROOF,
latest_accepted_at: candidate.latest_accepted_at,
})
}
async fn fetch_hero_candidate_row(
db: &difflore_core::SqlitePool,
normalized_repos: Option<&[String]>,
) -> Option<HeroCandidateRow> {
let repo_filter = normalized_repos
.filter(|repos| !repos.is_empty())
.map(|repos| {
let placeholders = std::iter::repeat_n("?", repos.len())
.collect::<Vec<_>>()
.join(", ");
format!("AND LOWER(COALESCE(s.source_repo, '')) IN ({placeholders})")
})
.unwrap_or_default();
let sql = format!(
"SELECT f.rule_id AS rule_id,
COALESCE(NULLIF(s.name, ''), f.rule_name) AS title,
s.source_repo AS source_repo,
COUNT(*) AS accepted_edits,
COUNT(DISTINCT NULLIF(f.diff_signature, '')) AS signed_diff_proofs,
MAX(f.created_at) AS latest_accepted_at
FROM fix_outcomes f
INNER JOIN skills s ON s.id = f.rule_id
WHERE f.accepted = 1
AND f.applied_ok = 1
AND f.rule_id IS NOT NULL
AND COALESCE(s.status, 'active') = 'active'
AND datetime(f.created_at) >= datetime('now', ?)
{repo_filter}
GROUP BY f.rule_id, COALESCE(NULLIF(s.name, ''), f.rule_name), s.source_repo
ORDER BY COUNT(DISTINCT NULLIF(f.diff_signature, '')) DESC,
COUNT(*) DESC,
datetime(MAX(f.created_at)) DESC,
title ASC
LIMIT 1"
);
let mut query = sqlx::query_as::<_, HeroCandidateRow>(&sql)
.bind(format!("-{LOCAL_PROOF_WINDOW_DAYS} days"));
if let Some(repos) = normalized_repos {
for repo in repos {
query = query.bind(repo);
}
}
query.fetch_optional(db).await.ok().flatten()
}
async fn fetch_hero_accepted_fix(
db: &difflore_core::SqlitePool,
rule_id: &str,
) -> Option<HeroAcceptedFixRow> {
sqlx::query_as::<_, HeroAcceptedFixRow>(
"SELECT NULLIF(repo_full_name, '') AS target_repo_full_name,
pr_number AS target_pr_number,
NULLIF(file_path, '') AS sample_file
FROM fix_outcomes
WHERE rule_id = ?1
AND accepted = 1
AND applied_ok = 1
AND datetime(created_at) >= datetime('now', ?2)
ORDER BY datetime(created_at) DESC, id DESC
LIMIT 1",
)
.bind(rule_id)
.bind(format!("-{LOCAL_PROOF_WINDOW_DAYS} days"))
.fetch_optional(db)
.await
.ok()
.flatten()
}
#[cfg(test)]
mod tests {
use super::super::test_support::{insert_skill_only, value_loop_pool};
use super::*;
#[tokio::test]
async fn local_hero_evidence_is_current_repo_only() {
let pool = value_loop_pool().await;
insert_skill_only(
&pool,
"repo-rule",
"Prefer structured API parsing",
"acme/widgets",
)
.await;
insert_skill_only(
&pool,
"global-rule",
"Pin GitHub Actions refs to SHAs",
"tanstack/router",
)
.await;
sqlx::query(
"INSERT INTO fix_outcomes
(id, rule_id, rule_name, file_path, repo_full_name, pr_number,
diff_signature, accepted, applied_ok, created_at)
VALUES
('repo-fix-1', 'repo-rule', 'Prefer structured API parsing', 'src/parser.rs',
'acme/widgets', 12, 'sha256:repo', 1, 1, datetime('now')),
('global-fix-1', 'global-rule', 'Pin GitHub Actions refs to SHAs',
'.github/workflows/pr.yml', 'difflore-fixtures/router', 4, 'sha256:one',
1, 1, datetime('now')),
('global-fix-2', 'global-rule', 'Pin GitHub Actions refs to SHAs',
'.github/workflows/release.yml', 'difflore-fixtures/router', 4, 'sha256:two',
1, 1, datetime('now'))",
)
.execute(&pool)
.await
.expect("insert accepted fixes");
difflore_core::observability::rule_outcomes::record_recalled_with_context(
&pool,
&[
difflore_core::observability::rule_outcomes::RuleRecallInput {
rule_id: "repo-rule",
session_id: Some("session-repo"),
repo_full_name: Some("acme/widgets"),
file_path: Some("src/parser.rs"),
query_text: "structured parser",
rank: 1,
top_k: 3,
strict_file_match: true,
},
],
)
.await
.expect("record repo recall");
difflore_core::observability::mcp_rule_serves::record(
&pool,
&difflore_core::observability::mcp_rule_serves::McpRuleServeInput {
tool: "search_rules",
session_id: Some("session-repo"),
repo_full_name: Some("acme/widgets"),
file_path: Some("src/parser.rs"),
query_text: "structured parser",
rule_ids: &["repo-rule".to_owned()],
top_k: 3,
strict_match_count: 1,
estimated_tokens: 100,
},
)
.await
.expect("record repo mcp serve");
let scoped = local_hero_evidence(&pool, &["acme/widgets".to_owned()])
.await
.expect("current repo hero");
assert_eq!(scoped.scope, "currentRepo");
assert_eq!(scoped.rule_id, "repo-rule");
assert_eq!(scoped.accepted_edits, 1);
assert_eq!(scoped.recall_events, 1);
assert_eq!(scoped.strict_agent_serves, 1);
let scoped_none = local_hero_evidence(&pool, &["missing/repo".to_owned()]).await;
assert!(
scoped_none.is_none(),
"known repo scopes must not fall back to unrelated local hero evidence"
);
let no_scope = local_hero_evidence(&pool, &[]).await;
assert!(
no_scope.is_none(),
"status must not show a best-on-machine hero when no repo scope is known"
);
}
}