Skip to main content

mdx_rust_core/
artifact.rs

1//! Machine-readable explanations for mdx-rust artifacts.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
8pub struct ArtifactExplanation {
9    pub schema_version: String,
10    pub artifact_path: String,
11    pub artifact_kind: ArtifactKind,
12    pub summary: String,
13    pub mutates_source: bool,
14    pub recommended_next_actions: Vec<String>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
18pub enum ArtifactKind {
19    AgentContract,
20    AuditPacket,
21    AutopilotRun,
22    CodebaseMap,
23    EvidenceRun,
24    EvolutionScorecard,
25    HardeningRun,
26    RefactorApplyRun,
27    RefactorBatchApplyRun,
28    RefactorPlan,
29    Unknown,
30}
31
32pub fn explain_artifact(path: &Path) -> anyhow::Result<ArtifactExplanation> {
33    let content = std::fs::read_to_string(path)?;
34    let value: serde_json::Value = serde_json::from_str(&content)?;
35    let kind = artifact_kind(&value);
36    let summary = artifact_summary(&kind, &value);
37    let recommended_next_actions = artifact_next_actions(&kind, &value);
38
39    Ok(ArtifactExplanation {
40        schema_version: "0.9".to_string(),
41        artifact_path: path.display().to_string(),
42        artifact_kind: kind,
43        summary,
44        mutates_source: false,
45        recommended_next_actions,
46    })
47}
48
49fn artifact_kind(value: &serde_json::Value) -> ArtifactKind {
50    if value.get("commands").is_some() && value.get("json_mode_contract").is_some() {
51        ArtifactKind::AgentContract
52    } else if value.get("autopilot").is_some() || value.get("edit_scope_contract").is_some() {
53        ArtifactKind::AuditPacket
54    } else if value.get("passes").is_some() && value.get("execution_summary").is_some() {
55        ArtifactKind::AutopilotRun
56    } else if value.get("scorecard_id").is_some() {
57        ArtifactKind::EvolutionScorecard
58    } else if value.get("map_id").is_some() {
59        ArtifactKind::CodebaseMap
60    } else if value.get("run_id").is_some() && value.get("unlocked_recipe_tiers").is_some() {
61        ArtifactKind::EvidenceRun
62    } else if value.get("outcome").is_some() && value.get("changes").is_some() {
63        ArtifactKind::HardeningRun
64    } else if value.get("plan_id").is_some() && value.get("candidate_id").is_some() {
65        ArtifactKind::RefactorApplyRun
66    } else if value.get("plan_id").is_some() && value.get("steps").is_some() {
67        ArtifactKind::RefactorBatchApplyRun
68    } else if value.get("plan_id").is_some() && value.get("candidates").is_some() {
69        ArtifactKind::RefactorPlan
70    } else {
71        ArtifactKind::Unknown
72    }
73}
74
75fn artifact_summary(kind: &ArtifactKind, value: &serde_json::Value) -> String {
76    match kind {
77        ArtifactKind::AutopilotRun => format!(
78            "autopilot {:?}: {} executed, {} planned",
79            value.get("status").and_then(|value| value.as_str()),
80            value
81                .get("total_executed_candidates")
82                .and_then(|value| value.as_u64())
83                .unwrap_or(0),
84            value
85                .get("total_planned_candidates")
86                .and_then(|value| value.as_u64())
87                .unwrap_or(0)
88        ),
89        ArtifactKind::CodebaseMap => format!(
90            "codebase map with quality {:?} and security score {}",
91            value
92                .pointer("/quality/grade")
93                .and_then(|value| value.as_str()),
94            value
95                .pointer("/security/score")
96                .and_then(|value| value.as_u64())
97                .unwrap_or(0)
98        ),
99        ArtifactKind::EvidenceRun => format!(
100            "evidence run graded {:?} with {} profiled file(s)",
101            value.get("grade").and_then(|value| value.as_str()),
102            value
103                .get("file_profiles")
104                .and_then(|value| value.as_array())
105                .map(|profiles| profiles.len())
106                .unwrap_or(0)
107        ),
108        ArtifactKind::EvolutionScorecard => format!(
109            "evolution scorecard {:?}: {} executable candidate(s)",
110            value
111                .pointer("/readiness/grade")
112                .and_then(|value| value.as_str()),
113            value
114                .pointer("/readiness/executable_candidates")
115                .and_then(|value| value.as_u64())
116                .unwrap_or(0)
117        ),
118        ArtifactKind::HardeningRun => format!(
119            "hardening run {:?} with {} proposed change(s)",
120            value
121                .pointer("/outcome/status")
122                .and_then(|value| value.as_str()),
123            value
124                .get("changes")
125                .and_then(|value| value.as_array())
126                .map(|changes| changes.len())
127                .unwrap_or(0)
128        ),
129        ArtifactKind::RefactorPlan => format!(
130            "refactor plan with {} candidate(s) and evidence {:?}",
131            value
132                .get("candidates")
133                .and_then(|value| value.as_array())
134                .map(|candidates| candidates.len())
135                .unwrap_or(0),
136            value
137                .pointer("/evidence/grade")
138                .and_then(|value| value.as_str())
139        ),
140        ArtifactKind::RefactorApplyRun | ArtifactKind::RefactorBatchApplyRun => format!(
141            "plan application {:?}",
142            value.get("status").and_then(|value| value.as_str())
143        ),
144        ArtifactKind::AgentContract => "agent command contract for mdx-rust".to_string(),
145        ArtifactKind::AuditPacket => "optimization audit packet".to_string(),
146        ArtifactKind::Unknown => "unrecognized mdx-rust JSON artifact".to_string(),
147    }
148}
149
150fn artifact_next_actions(kind: &ArtifactKind, value: &serde_json::Value) -> Vec<String> {
151    match kind {
152        ArtifactKind::EvidenceRun => vec![
153            "Run mdx-rust --json map <target> to see evidence-gated risk and recipe eligibility."
154                .to_string(),
155            "Run mdx-rust --json plan <target> before applying autonomous changes.".to_string(),
156        ],
157        ArtifactKind::CodebaseMap => vec![
158            "Inspect recommended_actions and capability_gates before mutation.".to_string(),
159            "Run mdx-rust --json plan <target> to create a stale-checked plan.".to_string(),
160        ],
161        ArtifactKind::EvolutionScorecard => vec![
162            "Inspect readiness and next_commands before choosing mutation.".to_string(),
163            "Only add --apply to suggested commands after explicit human approval.".to_string(),
164        ],
165        ArtifactKind::RefactorPlan => {
166            let has_executable = value
167                .get("candidates")
168                .and_then(|value| value.as_array())
169                .is_some_and(|candidates| {
170                    candidates.iter().any(|candidate| {
171                        candidate.get("status").and_then(|value| value.as_str())
172                            == Some("ApplyViaImprove")
173                    })
174                });
175            if has_executable {
176                vec![
177                    "Run mdx-rust --json apply-plan <artifact> --all for review mode.".to_string(),
178                    "Only add --apply when the user explicitly approves source mutation."
179                        .to_string(),
180                ]
181            } else {
182                vec![
183                    "No executable candidates are present; treat this as a design review plan."
184                        .to_string(),
185                ]
186            }
187        }
188        ArtifactKind::AutopilotRun => vec![
189            "Read execution_summary before reporting progress to a human.".to_string(),
190            "Use artifact_path values to inspect nested plan and hardening reports.".to_string(),
191        ],
192        ArtifactKind::HardeningRun
193        | ArtifactKind::RefactorApplyRun
194        | ArtifactKind::RefactorBatchApplyRun => {
195            vec![
196                "Inspect validation and rollback records before claiming a change landed."
197                    .to_string(),
198            ]
199        }
200        ArtifactKind::AgentContract | ArtifactKind::AuditPacket | ArtifactKind::Unknown => {
201            vec![
202                "Use mdx-rust --json agent-contract before selecting the next command.".to_string(),
203            ]
204        }
205    }
206}