use crate::AppState;
use crate::analysis_handles::{analysis_section_handles, analysis_summary_resource};
use crate::state::AnalysisArtifact;
use serde_json::{Value, json};
use std::collections::BTreeMap;
pub(crate) fn analysis_resource_entries(state: &AppState) -> Vec<Value> {
let mut items = vec![
json!({
"uri": "codelens://analysis/recent",
"name": "Recent Analyses",
"description": "Recent stored analyses with summary resource handles",
"mimeType": "application/json"
}),
json!({
"uri": "codelens://analysis/jobs",
"name": "Analysis Jobs",
"description": "Queued and completed analysis jobs with jump handles",
"mimeType": "application/json"
}),
];
items.extend(
state
.list_analysis_summaries()
.into_iter()
.map(|artifact| {
json!({
"uri": format!("codelens://analysis/{}/summary", artifact.id),
"name": format!("Analysis: {}", artifact.tool_name),
"description": format!("{} ({})", artifact.summary, artifact.surface),
"mimeType": "application/json"
})
})
.collect::<Vec<_>>(),
);
items
}
pub(crate) fn recent_analysis_payload(state: &AppState) -> Value {
let mut summaries = state.list_analysis_summaries();
summaries.sort_by(|a, b| b.created_at_ms.cmp(&a.created_at_ms));
let mut tool_counts = BTreeMap::new();
for summary in &summaries {
*tool_counts
.entry(summary.tool_name.clone())
.or_insert(0usize) += 1;
}
let latest_created_at_ms = summaries
.iter()
.map(|summary| summary.created_at_ms)
.max()
.unwrap_or_default();
let items = summaries
.iter()
.take(8)
.map(|summary| {
json!({
"analysis_id": summary.id,
"tool_name": summary.tool_name,
"summary": summary.summary,
"surface": summary.surface,
"created_at_ms": summary.created_at_ms,
"summary_resource": analysis_summary_resource(&summary.id),
})
})
.collect::<Vec<_>>();
json!({
"artifacts": items,
"count": summaries.len(),
"latest_created_at_ms": latest_created_at_ms,
"tool_counts": tool_counts,
})
}
pub(crate) fn recent_analysis_jobs_payload(state: &AppState) -> Value {
let scope = state.current_project_scope();
let mut jobs = state.list_analysis_jobs_for_scope(&scope, None);
jobs.sort_by(|a, b| b.updated_at_ms.cmp(&a.updated_at_ms));
let mut status_counts = BTreeMap::new();
for job in &jobs {
*status_counts
.entry(job.status.as_str().to_owned())
.or_insert(0usize) += 1;
}
let items = jobs
.iter()
.take(8)
.map(|job| {
let section_handles = job
.analysis_id
.as_deref()
.map(|analysis_id| analysis_section_handles(analysis_id, &job.estimated_sections))
.unwrap_or_else(|| json!([]));
let summary_resource = job
.analysis_id
.as_deref()
.map(analysis_summary_resource)
.unwrap_or(Value::Null);
json!({
"job_id": job.id,
"kind": job.kind,
"status": job.status,
"progress": job.progress,
"current_step": job.current_step,
"analysis_id": job.analysis_id,
"estimated_sections": job.estimated_sections,
"summary_resource": summary_resource,
"section_handles": section_handles,
"updated_at_ms": job.updated_at_ms,
})
})
.collect::<Vec<_>>();
json!({
"jobs": items,
"count": jobs.len(),
"active_count": jobs.iter().filter(|job| matches!(job.status, crate::runtime_types::JobLifecycle::Queued | crate::runtime_types::JobLifecycle::Running)).count(),
"status_counts": status_counts,
})
}
pub(crate) fn analysis_summary_payload(artifact: &AnalysisArtifact) -> Value {
let verifier_checks = if artifact.verifier_checks.is_empty() {
vec![
json!({
"check": "diagnostic_verifier",
"status": artifact.readiness.diagnostics_ready,
"summary": "Refresh diagnostics evidence before trusting a reused artifact.",
"evidence_section": null,
}),
json!({
"check": "reference_verifier",
"status": artifact.readiness.reference_safety,
"summary": "Refresh reference evidence before mutating reused analysis targets.",
"evidence_section": null,
}),
json!({
"check": "test_readiness_verifier",
"status": artifact.readiness.test_readiness,
"summary": "Refresh test-readiness evidence before relying on a reused artifact.",
"evidence_section": null,
}),
json!({
"check": "mutation_readiness_verifier",
"status": artifact.readiness.mutation_ready,
"summary": if artifact.blockers.is_empty() {
"Reused artifact needs fresh verifier evidence before mutation."
} else {
"Blockers remain on the reused artifact; refresh evidence before mutation."
},
"evidence_section": null,
}),
]
} else {
artifact
.verifier_checks
.iter()
.map(|check| {
json!({
"check": check.check,
"status": check.status,
"summary": check.summary,
"evidence_section": check.evidence_section,
})
})
.collect::<Vec<_>>()
};
let quality_focus = infer_summary_quality_focus(
&artifact.tool_name,
&artifact.summary,
&artifact.top_findings,
);
let recommended_checks = infer_summary_recommended_checks(
&artifact.tool_name,
&artifact.summary,
&artifact.top_findings,
&artifact.next_actions,
&artifact.available_sections,
);
let performance_watchpoints = infer_summary_performance_watchpoints(
&artifact.summary,
&artifact.top_findings,
&artifact.next_actions,
);
let summary_resource = analysis_summary_resource(&artifact.id);
let section_handles = analysis_section_handles(&artifact.id, &artifact.available_sections);
let mut payload = json!({
"analysis_id": artifact.id,
"tool_name": artifact.tool_name,
"surface": artifact.surface,
"summary": artifact.summary,
"top_findings": artifact.top_findings,
"risk_level": artifact.risk_level,
"confidence": artifact.confidence,
"next_actions": artifact.next_actions,
"blockers": artifact.blockers,
"blocker_count": artifact.blockers.len(),
"readiness": artifact.readiness,
"verifier_checks": verifier_checks,
"quality_focus": quality_focus,
"recommended_checks": recommended_checks,
"performance_watchpoints": performance_watchpoints,
"available_sections": artifact.available_sections,
"summary_resource": summary_resource,
"section_handles": section_handles,
"created_at_ms": artifact.created_at_ms,
});
if artifact.surface == "ci-audit" {
payload["schema_version"] = json!("codelens-ci-audit-v1");
payload["report_kind"] = json!(artifact.tool_name);
payload["profile"] = json!("ci-audit");
payload["machine_summary"] = json!({
"finding_count": artifact.top_findings.len(),
"next_action_count": artifact.next_actions.len(),
"section_count": artifact.available_sections.len(),
"blocker_count": artifact.blockers.len(),
"verifier_check_count": payload["verifier_checks"].as_array().map(|v| v.len()).unwrap_or(0),
"ready_check_count": payload["verifier_checks"].as_array().map(|checks| checks.iter().filter(|check| check.get("status").and_then(|value| value.as_str()) == Some("ready")).count()).unwrap_or(0),
"blocked_check_count": payload["verifier_checks"].as_array().map(|checks| checks.iter().filter(|check| check.get("status").and_then(|value| value.as_str()) == Some("blocked")).count()).unwrap_or(0),
"quality_focus_count": payload["quality_focus"].as_array().map(|v| v.len()).unwrap_or(0),
"recommended_check_count": payload["recommended_checks"].as_array().map(|v| v.len()).unwrap_or(0),
"performance_watchpoint_count": payload["performance_watchpoints"].as_array().map(|v| v.len()).unwrap_or(0),
});
payload["evidence_handles"] = payload["section_handles"].clone();
}
payload
}
fn infer_summary_quality_focus(
tool_name: &str,
summary: &str,
top_findings: &[String],
) -> Vec<String> {
let combined = format!("{} {}", summary, top_findings.join(" ")).to_ascii_lowercase();
let mut focus = Vec::new();
let mut push_unique = |value: &str| {
if !focus.iter().any(|existing| existing == value) {
focus.push(value.to_owned());
}
};
push_unique("correctness");
if matches!(
tool_name,
"explore_codebase"
| "trace_request_path"
| "review_architecture"
| "plan_safe_refactor"
| "audit_security_context"
| "analyze_change_impact"
| "cleanup_duplicate_logic"
| "onboard_project"
| "analyze_change_request"
| "verify_change_readiness"
| "impact_report"
| "refactor_safety_report"
| "safe_rename_report"
| "unresolved_reference_check"
) {
push_unique("regression_safety");
}
if combined.contains("http")
|| combined.contains("browser")
|| combined.contains("ui")
|| combined.contains("render")
|| combined.contains("frontend")
|| combined.contains("layout")
{
push_unique("user_experience");
}
if combined.contains("coupling")
|| combined.contains("circular")
|| combined.contains("refactor")
|| combined.contains("boundary")
{
push_unique("maintainability");
}
if combined.contains("search")
|| combined.contains("embedding")
|| combined.contains("watch")
|| combined.contains("latency")
|| combined.contains("performance")
{
push_unique("performance");
}
focus
}
fn infer_summary_recommended_checks(
tool_name: &str,
summary: &str,
top_findings: &[String],
next_actions: &[String],
available_sections: &[String],
) -> Vec<String> {
let combined = format!(
"{} {} {} {}",
tool_name,
summary,
top_findings.join(" "),
next_actions.join(" ")
)
.to_ascii_lowercase();
let mut checks = Vec::new();
let mut push_unique = |value: &str| {
if !checks.iter().any(|existing| existing == value) {
checks.push(value.to_owned());
}
};
push_unique("run targeted tests for affected files or symbols");
push_unique("run diagnostics or lint on touched files before finalizing");
if available_sections
.iter()
.any(|section| section == "related_tests")
{
push_unique("expand related_tests and execute the highest-signal subset");
}
if combined.contains("rename") || combined.contains("refactor") {
push_unique("verify references and call sites after the refactor preview");
}
if combined.contains("http")
|| combined.contains("browser")
|| combined.contains("ui")
|| combined.contains("frontend")
|| combined.contains("layout")
|| combined.contains("render")
{
push_unique("exercise the user-facing flow in a browser or UI harness");
}
if combined.contains("search")
|| combined.contains("embedding")
|| combined.contains("latency")
|| combined.contains("performance")
{
push_unique("compare hot-path latency or throughput before and after the change");
}
if combined.contains("dead code") || combined.contains("delete") {
push_unique("confirm the candidate is unused in tests, runtime paths, and CI scripts");
}
checks
}
fn infer_summary_performance_watchpoints(
summary: &str,
top_findings: &[String],
next_actions: &[String],
) -> Vec<String> {
let combined = format!(
"{} {} {}",
summary,
top_findings.join(" "),
next_actions.join(" ")
)
.to_ascii_lowercase();
let mut watchpoints = Vec::new();
let mut push_unique = |value: &str| {
if !watchpoints.iter().any(|existing| existing == value) {
watchpoints.push(value.to_owned());
}
};
if combined.contains("search") || combined.contains("embedding") || combined.contains("query") {
push_unique("watch ranking quality, latency, and cache-hit behavior on search paths");
}
if combined.contains("http") || combined.contains("server") || combined.contains("route") {
push_unique("watch request latency, concurrency, and error-rate changes on hot routes");
}
if combined.contains("watch") || combined.contains("filesystem") {
push_unique("watch background work, queue depth, and repeated invalidation behavior");
}
if combined.contains("ui")
|| combined.contains("frontend")
|| combined.contains("layout")
|| combined.contains("render")
|| combined.contains("browser")
{
push_unique("watch rendering smoothness, layout stability, and unnecessary re-renders");
}
watchpoints
}