use clap::{Parser, Subcommand};
use mdx_rust_core::Config;
#[derive(Parser)]
#[command(name = "mdx-rust")]
#[command(version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
#[arg(long, global = true)]
json: bool,
#[arg(long, global = true, env = "MDX_RUST_CONFIG")]
config: Option<String>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Init,
Register {
name: String,
path: Option<String>,
},
Spec {
name: String,
},
Optimize {
name: String,
#[arg(long, default_value = "3")]
iterations: u32,
#[arg(long, default_value = "medium", value_parser = ["light", "medium", "heavy"])]
budget: String,
#[arg(long)]
review: bool,
},
AgentContract,
Recipes,
Explain {
artifact: String,
},
Scorecard {
target: Option<String>,
#[arg(long)]
policy: Option<String>,
#[arg(long)]
eval_spec: Option<String>,
#[arg(long, default_value = "250")]
max_files: usize,
},
Evidence {
target: Option<String>,
#[arg(long)]
include_coverage: bool,
#[arg(long)]
include_mutation: bool,
#[arg(long)]
include_semver: bool,
#[arg(long, default_value = "180")]
timeout_seconds: u64,
},
Doctor {
name: Option<String>,
},
Improve {
target: Option<String>,
#[arg(long)]
goal: Option<String>,
#[arg(long)]
policy: Option<String>,
#[arg(long)]
eval_spec: Option<String>,
#[arg(long)]
apply: bool,
#[arg(long, default_value = "100")]
max_files: usize,
#[arg(long, default_value = "1", value_parser = ["1", "2", "tier1", "tier2"])]
tier: String,
#[arg(long, default_value = "180")]
timeout_seconds: u64,
},
Plan {
target: Option<String>,
#[arg(long)]
policy: Option<String>,
#[arg(long)]
eval_spec: Option<String>,
#[arg(long, default_value = "100")]
max_files: usize,
},
Map {
target: Option<String>,
#[arg(long)]
policy: Option<String>,
#[arg(long)]
eval_spec: Option<String>,
#[arg(long, default_value = "250")]
max_files: usize,
},
Autopilot {
target: Option<String>,
#[arg(long)]
policy: Option<String>,
#[arg(long)]
eval_spec: Option<String>,
#[arg(long)]
apply: bool,
#[arg(long)]
allow_public_api_impact: bool,
#[arg(long, default_value = "250")]
max_files: usize,
#[arg(long, default_value = "3")]
max_passes: usize,
#[arg(long, default_value = "25")]
max_candidates: usize,
#[arg(long, default_value = "1", value_parser = ["1", "2", "3", "tier1", "tier2", "tier3"])]
tier: String,
#[arg(long, default_value = "compiled", value_parser = ["none", "compiled", "tested", "covered", "hardened", "proven"])]
min_evidence: String,
#[arg(long)]
budget: Option<String>,
#[arg(long, default_value = "180")]
timeout_seconds: u64,
},
Evolve {
target: Option<String>,
#[arg(long)]
policy: Option<String>,
#[arg(long)]
eval_spec: Option<String>,
#[arg(long)]
apply: bool,
#[arg(long, default_value = "10m")]
budget: String,
#[arg(long, default_value = "1", value_parser = ["1", "2", "3", "tier1", "tier2", "tier3"])]
tier: String,
#[arg(long, default_value = "compiled", value_parser = ["none", "compiled", "tested", "covered", "hardened", "proven"])]
min_evidence: String,
#[arg(long, default_value = "250")]
max_files: usize,
#[arg(long, default_value = "25")]
max_candidates: usize,
#[arg(long)]
allow_public_api_impact: bool,
},
ApplyPlan {
plan: String,
#[arg(long)]
candidate: Option<String>,
#[arg(long, conflicts_with = "candidate")]
all: bool,
#[arg(long)]
apply: bool,
#[arg(long)]
allow_public_api_impact: bool,
#[arg(long, default_value = "180")]
timeout_seconds: u64,
#[arg(long, default_value = "25")]
max_candidates: usize,
},
Eval {
name: Option<String>,
#[arg(long)]
dataset: Option<String>,
#[arg(long)]
spec: Option<String>,
},
Audit {
name: Option<String>,
#[arg(long)]
policy: Option<String>,
},
Schema {
#[arg(value_parser = ["agent-contract", "artifact-explanation", "audit-packet", "candidate", "optimization-run", "hook-decision", "trace-event", "hardening-run", "hardening-finding", "behavior-eval-report", "project-policy", "evidence-run", "recipe-catalog", "evolution-scorecard", "refactor-plan", "refactor-apply-run", "refactor-batch-apply-run", "codebase-map", "autopilot-run"])]
kind: String,
},
#[clap(hide = true)]
Invoke {
name: String,
#[arg(long)]
input: Option<String>,
},
}
fn main() {
let cli = Cli::parse();
init_tracing(cli.json);
match cli.command {
Commands::Init => {
if let Err(e) = cmd_init(cli.json) {
emit_error(cli.json, "init", &e);
std::process::exit(1);
}
}
Commands::Register { name, path } => {
if let Err(e) = cmd_register(&name, path.as_deref(), cli.json) {
emit_error(cli.json, "register", &e);
std::process::exit(1);
}
}
Commands::Spec { name } => {
if let Err(e) = cmd_spec(&name, cli.json) {
emit_error(cli.json, "spec", &e);
std::process::exit(1);
}
}
Commands::Optimize {
name,
iterations,
budget,
review,
..
} => {
if let Err(e) = cmd_optimize(&name, iterations, &budget, review, cli.json) {
emit_error(cli.json, "optimize", &e);
std::process::exit(1);
}
}
Commands::AgentContract => {
if let Err(e) = cmd_agent_contract(cli.json) {
emit_error(cli.json, "agent-contract", &e);
std::process::exit(1);
}
}
Commands::Recipes => {
if let Err(e) = cmd_recipes(cli.json) {
emit_error(cli.json, "recipes", &e);
std::process::exit(1);
}
}
Commands::Explain { artifact } => {
if let Err(e) = cmd_explain(&artifact, cli.json) {
emit_error(cli.json, "explain", &e);
std::process::exit(1);
}
}
Commands::Scorecard {
target,
policy,
eval_spec,
max_files,
} => {
if let Err(e) = cmd_scorecard(
target.as_deref(),
policy.as_deref(),
eval_spec.as_deref(),
max_files,
cli.json,
) {
emit_error(cli.json, "scorecard", &e);
std::process::exit(1);
}
}
Commands::Evidence {
target,
include_coverage,
include_mutation,
include_semver,
timeout_seconds,
} => {
if let Err(e) = cmd_evidence(EvidenceCommand {
target: target.as_deref(),
include_coverage,
include_mutation,
include_semver,
timeout_seconds,
json: cli.json,
}) {
emit_error(cli.json, "evidence", &e);
std::process::exit(1);
}
}
Commands::Doctor { name } => {
if let Err(e) = cmd_doctor(name.as_deref(), cli.json) {
emit_error(cli.json, "doctor", &e);
std::process::exit(1);
}
}
Commands::Improve {
target,
goal,
policy,
eval_spec,
apply,
max_files,
tier,
timeout_seconds,
} => {
if let Err(e) = cmd_improve(ImproveCommand {
target: target.as_deref(),
goal: goal.as_deref(),
policy: policy.as_deref(),
eval_spec: eval_spec.as_deref(),
apply,
max_files,
tier: &tier,
timeout_seconds,
json: cli.json,
}) {
emit_error(cli.json, "improve", &e);
std::process::exit(1);
}
}
Commands::Plan {
target,
policy,
eval_spec,
max_files,
} => {
if let Err(e) = cmd_plan(
target.as_deref(),
policy.as_deref(),
eval_spec.as_deref(),
max_files,
cli.json,
) {
emit_error(cli.json, "plan", &e);
std::process::exit(1);
}
}
Commands::Map {
target,
policy,
eval_spec,
max_files,
} => {
if let Err(e) = cmd_map(
target.as_deref(),
policy.as_deref(),
eval_spec.as_deref(),
max_files,
cli.json,
) {
emit_error(cli.json, "map", &e);
std::process::exit(1);
}
}
Commands::Autopilot {
target,
policy,
eval_spec,
apply,
allow_public_api_impact,
max_files,
max_passes,
max_candidates,
tier,
min_evidence,
budget,
timeout_seconds,
} => {
if let Err(e) = cmd_autopilot(AutopilotCommand {
target: target.as_deref(),
policy: policy.as_deref(),
eval_spec: eval_spec.as_deref(),
apply,
allow_public_api_impact,
max_files,
max_passes,
max_candidates,
tier: &tier,
min_evidence: &min_evidence,
budget: budget.as_deref(),
timeout_seconds,
json: cli.json,
}) {
emit_error(cli.json, "autopilot", &e);
std::process::exit(1);
}
}
Commands::Evolve {
target,
policy,
eval_spec,
apply,
budget,
tier,
min_evidence,
max_files,
max_candidates,
allow_public_api_impact,
} => {
let budget_duration = match parse_budget(&budget) {
Ok(duration) => duration,
Err(e) => {
emit_error(cli.json, "evolve", &e);
std::process::exit(1);
}
};
let max_passes = max_passes_for_budget(budget_duration);
if let Err(e) = cmd_autopilot(AutopilotCommand {
target: target.as_deref(),
policy: policy.as_deref(),
eval_spec: eval_spec.as_deref(),
apply,
allow_public_api_impact,
max_files,
max_passes,
max_candidates,
tier: &tier,
min_evidence: &min_evidence,
budget: Some(&budget),
timeout_seconds: budget_duration.as_secs().clamp(30, 180),
json: cli.json,
}) {
emit_error(cli.json, "evolve", &e);
std::process::exit(1);
}
}
Commands::ApplyPlan {
plan,
candidate,
all,
apply,
allow_public_api_impact,
timeout_seconds,
max_candidates,
} => {
if let Err(e) = cmd_apply_plan(ApplyPlanCommand {
plan_path: &plan,
candidate_id: candidate.as_deref(),
all,
apply,
allow_public_api_impact,
timeout_seconds,
max_candidates,
json: cli.json,
}) {
emit_error(cli.json, "apply-plan", &e);
std::process::exit(1);
}
}
Commands::Eval {
name,
dataset,
spec,
} => {
if let Err(e) = cmd_eval(
name.as_deref(),
dataset.as_deref(),
spec.as_deref(),
cli.json,
) {
emit_error(cli.json, "eval", &e);
std::process::exit(1);
}
}
Commands::Audit { name, policy } => {
if let Err(e) = cmd_audit(name.as_deref(), policy.as_deref(), cli.json) {
emit_error(cli.json, "audit", &e);
std::process::exit(1);
}
}
Commands::Schema { kind } => {
if let Err(e) = cmd_schema(&kind, cli.json) {
emit_error(cli.json, "schema", &e);
std::process::exit(1);
}
}
Commands::Invoke { name, input } => {
if let Err(e) = cmd_invoke(&name, input.as_deref(), cli.json) {
emit_error(cli.json, "invoke", &e);
std::process::exit(1);
}
}
}
}
fn emit_error(json: bool, command: &str, error: &anyhow::Error) {
let suggestion = error_suggestion(command, &error.to_string());
if json {
println!(
"{}",
serde_json::json!({
"status": "error",
"command": command,
"error": error.to_string(),
"suggestion": suggestion
})
);
} else {
eprintln!("{} error: {}", command, error);
if let Some(suggestion) = suggestion {
eprintln!("next step: {}", suggestion);
}
}
}
fn error_suggestion(command: &str, error: &str) -> Option<&'static str> {
if error.contains("not registered") {
return Some("run `mdx-rust register <name> <path>` and then retry");
}
if error.contains("No Cargo.toml") || error.contains("Cannot find Cargo.toml") {
return Some("point mdx-rust at a Rust crate root that contains Cargo.toml");
}
if error.contains("dataset") && command == "eval" {
return Some("run `mdx-rust spec <name>` to generate a starter dataset, or pass --dataset");
}
if error.contains("unknown optimization budget") {
return Some("use one of: light, medium, heavy");
}
None
}
fn init_tracing(json: bool) {
use tracing_subscriber::EnvFilter;
let filter = EnvFilter::from_default_env().add_directive(
"mdx_rust=info"
.parse()
.unwrap_or_else(|_| "info".parse().unwrap()),
);
if json {
tracing_subscriber::fmt()
.with_env_filter(filter)
.json()
.init();
} else {
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_target(false)
.compact()
.init();
}
}
fn cmd_init(json: bool) -> anyhow::Result<()> {
use std::fs;
use std::path::Path;
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd)?;
let artifact_dir = &config.artifact_dir;
if Path::new(artifact_dir).exists() {
if json {
println!(
"{}",
serde_json::json!({"status":"already_initialized","path":artifact_dir})
);
} else {
println!(
"mdx-rust is already initialized in this directory ({} exists).",
artifact_dir
);
}
return Ok(());
}
fs::create_dir(artifact_dir)?;
let config_content = r#"# mdx-rust configuration
# This file was generated by `mdx-rust init`. Edit freely.
# Artifact directory for mdx-rust state and reports.
artifact_dir = ".mdx-rust"
[models]
# analyzer = "claude-4-sonnet" # Used for deep diagnosis and candidate generation
# judge = "gpt-4o" # Used for LLM-as-Judge scoring
# default = "gpt-4o-mini"
"#;
fs::write(format!("{}/config.toml", artifact_dir), config_content)?;
let ignore_content = r#"# mdx-rust ignore file
# Patterns here (plus .gitignore) will be excluded when building code bundles for the LLM.
target/
**/*.rlib
**/*.rmeta
Cargo.lock
.idea/
.vscode/
.mdx-rust/
"#;
fs::write(format!("{}/.mdx-rustignore", artifact_dir), ignore_content)?;
let policies = r#"# Project Policy
## Purpose
[Describe the purpose of this Rust project in 1-2 sentences]
## Hardening Rules
1. Avoid panics in request, CLI, and service boundary paths.
2. Preserve contextual errors for filesystem, environment, network, and database failures.
3. Validate external inputs before using them in risky operations.
## Constraints
- ...
## Quality Expectations
- ...
"#;
fs::write(format!("{}/policies.md", artifact_dir), policies)?;
let eval_spec = r#"{
"version": 1,
"description": "Evaluation specification for this agent",
"scoring": { "fields": [] },
"policy_path": "policies.md"
}"#;
fs::write(format!("{}/eval_spec.json", artifact_dir), eval_spec)?;
let behavior_evals = r#"{
"version": "v1",
"commands": [
{
"id": "cargo-check",
"command": "cargo",
"args": ["check"],
"expect_success": true,
"timeout_seconds": 120
}
]
}"#;
fs::write(format!("{}/evals.json", artifact_dir), behavior_evals)?;
if json {
println!(
"{}",
serde_json::json!({"status":"initialized","artifact_dir":artifact_dir})
);
} else {
println!("✅ mdx-rust initialized in {}", cwd.display());
println!(" Artifact directory: {}", artifact_dir);
println!();
println!("Created files:");
println!(" {}/config.toml", artifact_dir);
println!(" {}/.mdx-rustignore", artifact_dir);
println!(" {}/policies.md", artifact_dir);
println!(" {}/eval_spec.json", artifact_dir);
println!(" {}/evals.json", artifact_dir);
println!();
println!("Next: mdx-rust doctor");
println!("Or: mdx-rust register <name> [path]");
}
Ok(())
}
struct EvidenceCommand<'a> {
target: Option<&'a str>,
include_coverage: bool,
include_mutation: bool,
include_semver: bool,
timeout_seconds: u64,
json: bool,
}
fn cmd_agent_contract(json: bool) -> anyhow::Result<()> {
let contract = mdx_rust_core::agent_contract();
if json {
println!("{}", serde_json::to_string_pretty(&contract)?);
return Ok(());
}
println!("🤖 mdx-rust agent contract");
println!(" Schema: {}", contract.schema_version);
println!(" Version: {}", contract.product_version);
println!(" JSON mode: {}", contract.json_mode_contract);
println!(" Mutation: {}", contract.mutation_contract);
println!(" Agent-safe commands:");
for command in &contract.commands {
let mutation = if command.mutates_source {
"mutation-capable"
} else {
"read-only"
};
println!(" - {} ({mutation}): {}", command.name, command.purpose);
}
println!(" Start with: mdx-rust --json agent-contract");
Ok(())
}
fn cmd_recipes(json: bool) -> anyhow::Result<()> {
let catalog = mdx_rust_core::recipe_catalog();
if json {
println!("{}", serde_json::to_string_pretty(&catalog)?);
return Ok(());
}
println!("🧪 mdx-rust recipes");
println!(" Schema: {}", catalog.schema_version);
for recipe in &catalog.recipes {
println!(
" - {} [{:?}] requires {:?} ({})",
recipe.id,
recipe.tier,
recipe.required_evidence,
if recipe.executable {
"executable"
} else {
"plan-only"
}
);
println!(" {}", recipe.description);
}
Ok(())
}
fn cmd_explain(artifact: &str, json: bool) -> anyhow::Result<()> {
let explanation = mdx_rust_core::explain_artifact(std::path::Path::new(artifact))?;
if json {
println!("{}", serde_json::to_string_pretty(&explanation)?);
return Ok(());
}
println!("🧾 mdx-rust explain");
println!(" Artifact: {}", explanation.artifact_path);
println!(" Kind: {:?}", explanation.artifact_kind);
println!(" Summary: {}", explanation.summary);
println!(" Next actions:");
for action in &explanation.recommended_next_actions {
println!(" - {}", action);
}
Ok(())
}
fn cmd_scorecard(
target: Option<&str>,
policy: Option<&str>,
eval_spec: Option<&str>,
max_files: usize,
json: bool,
) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
let scorecard = mdx_rust_core::build_evolution_scorecard(
&cwd,
Some(&artifact_root),
&mdx_rust_core::EvolutionScorecardConfig {
target: target.map(std::path::PathBuf::from),
policy_path: policy.map(std::path::PathBuf::from),
behavior_spec_path: eval_spec.map(std::path::PathBuf::from),
max_files,
},
)?;
if json {
println!("{}", serde_json::to_string_pretty(&scorecard)?);
return Ok(());
}
println!("📊 mdx-rust scorecard");
println!(" Scorecard: {}", scorecard.scorecard_id);
println!(" Root: {}", scorecard.root);
if let Some(target) = &scorecard.target {
println!(" Target: {}", target);
}
println!(
" Readiness: {:?} ({} executable, {} review-only, {} blocked)",
scorecard.readiness.grade,
scorecard.readiness.executable_candidates,
scorecard.readiness.review_only_candidates,
scorecard.readiness.blocked_candidates
);
println!(
" Quality: {:?} | debt={} | security={}",
scorecard.map.quality.grade, scorecard.map.quality.debt_score, scorecard.map.security.score
);
println!(" Next commands:");
for command in &scorecard.next_commands {
println!(" - {}", command);
}
if let Some(path) = &scorecard.artifact_path {
println!(" Artifact: {}", path);
}
Ok(())
}
fn cmd_evidence(args: EvidenceCommand<'_>) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
let run = mdx_rust_core::run_evidence(
&cwd,
Some(&artifact_root),
&mdx_rust_core::EvidenceRunConfig {
target: args.target.map(std::path::PathBuf::from),
include_coverage: args.include_coverage,
include_mutation: args.include_mutation,
include_semver: args.include_semver,
command_timeout: std::time::Duration::from_secs(args.timeout_seconds),
},
)?;
if args.json {
println!("{}", serde_json::to_string_pretty(&run)?);
return Ok(());
}
println!("📏 mdx-rust evidence");
println!(" Root: {}", run.root);
if let Some(target) = &run.target {
println!(" Target: {}", target);
}
println!(" Grade: {:?}", run.grade);
println!(" Analysis depth: {:?}", run.analysis_depth);
println!(" Profiled files: {}", run.file_profiles.len());
println!(" Note: {}", run.note);
for command in &run.commands {
let status = if command.skipped {
"skipped"
} else if command.success {
"passed"
} else if command.timed_out {
"timed out"
} else {
"failed"
};
println!(" - {}: {}", command.id, status);
if let Some(reason) = &command.skip_reason {
println!(" {}", reason);
}
}
if let Some(path) = &run.artifact_path {
println!(" Evidence artifact: {}", path);
}
Ok(())
}
fn cmd_doctor(name: Option<&str>, json: bool) -> anyhow::Result<()> {
if let Some(name) = name {
return cmd_agent_doctor(name, json);
}
cmd_workspace_doctor(json)
}
fn cmd_workspace_doctor(json: bool) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
let run = mdx_rust_core::run_hardening(
&cwd,
Some(&artifact_root),
&mdx_rust_core::HardeningConfig {
apply: false,
max_files: 100,
validation_timeout: std::time::Duration::from_secs(180),
..mdx_rust_core::HardeningConfig::default()
},
)?;
if json {
println!("{}", serde_json::to_string_pretty(&run)?);
return Ok(());
}
println!("🔍 mdx-rust doctor — workspace");
println!(" Root: {}", run.root);
println!(" Artifact directory: {}", artifact_root.display());
println!(
" Cargo metadata: {} package(s) ({})",
run.workspace.package_count,
if run.workspace.cargo_metadata_available {
"available"
} else {
"unavailable"
}
);
println!(" Scanned Rust files: {}", run.files_scanned);
println!(" Findings: {}", run.findings.len());
println!(
" Risk: high={}, medium={}, patchable={}",
run.risk_summary.high, run.risk_summary.medium, run.risk_summary.patchable
);
println!(" Proposed hardening changes: {}", run.changes.len());
println!(" Outcome: {:?}", run.outcome.status);
for recommendation in &run.risk_summary.top_recommendations {
println!(" → {}", recommendation);
}
if !run.changes.is_empty() {
println!();
println!("Suggested next step:");
println!(" mdx-rust improve --target <file-or-dir>");
println!(" mdx-rust improve --target <file-or-dir> --apply");
}
Ok(())
}
fn cmd_agent_doctor(name: &str, json: bool) -> anyhow::Result<()> {
use mdx_rust_core::registry::Registry;
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
let registry = Registry::load_from(&artifact_root).unwrap_or_default();
let _agent_dir = artifact_root.join("agents").join(name);
if json {
if let Some(agent) = registry.get(name) {
println!(
"{}",
serde_json::json!({
"agent": name,
"registered": true,
"path": agent.path.display().to_string()
})
);
} else {
println!("{}", serde_json::json!({"agent":name,"registered":false}));
}
return Ok(());
}
println!("🔍 mdx-rust doctor — agent '{}'", name);
println!(" Artifact directory: {}", artifact_root.display());
println!();
if !artifact_root.exists() {
println!(
" ❌ No {} directory. Run `mdx-rust init` first.",
config.artifact_dir
);
return Ok(());
}
println!(" ✅ {} exists", config.artifact_dir);
if let Some(agent) = registry.get(name) {
println!(" ✅ Agent is registered");
println!(" Path: {}", agent.path.display());
match mdx_rust_analysis::build_bundle_scope(&agent.path, None) {
Ok(scope) => {
println!(
" Would send ~{} files for LLM analysis",
scope.optimizable_paths.len()
);
}
Err(_) => {
println!(" (Could not compute bundle scope yet)");
}
}
} else {
println!(" ℹ️ Agent is not registered yet");
println!(" → mdx-rust register {}", name);
}
println!();
let best_dir = artifact_root.join("agents").join(name).join("best");
if best_dir.exists() && best_dir.join("src").exists() {
println!(" ✓ Best improved version available in best/ (from last accepted optimization)");
}
let exps = artifact_root.join("agents").join(name).join("experiments");
if exps.exists() {
if let Ok(entries) = std::fs::read_dir(&exps) {
let mut reports: Vec<_> = entries
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_string_lossy().starts_with("report-"))
.collect();
reports.sort_by_key(|e| std::fs::metadata(e.path()).and_then(|m| m.modified()).ok());
if !reports.is_empty() {
println!("Recent optimization runs:");
for r in reports.iter().rev().take(3) {
println!(" • {}", r.file_name().to_string_lossy());
}
}
}
}
Ok(())
}
fn cmd_register(name: &str, path: Option<&str>, json: bool) -> anyhow::Result<()> {
use mdx_rust_analysis::editing::run_command_with_timeout;
use mdx_rust_core::registry::{RegisteredAgent, Registry};
use std::path::Path;
use std::process::Command;
use std::time::Duration;
let cwd = std::env::current_dir()?;
let target_path = path
.map(Path::new)
.unwrap_or_else(|| Path::new("."))
.to_path_buf();
let cargo_toml = target_path.join("Cargo.toml");
if !cargo_toml.exists() {
anyhow::bail!(
"No Cargo.toml found at {}. Is this a Rust crate?",
target_path.display()
);
}
let config = Config::load_from_project(&cwd)?;
let artifact_root = cwd.join(&config.artifact_dir);
let mut registry = Registry::load_from(&artifact_root)?;
let contract = detect_contract(&target_path);
let contract_label = format!("{contract:?}");
let absolute_path = target_path.canonicalize().unwrap_or(target_path.clone());
let agent = RegisteredAgent {
name: name.to_string(),
path: absolute_path.clone(),
contract,
registered_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs().to_string())
.unwrap_or_else(|_| "0".to_string()),
};
registry.register(agent);
registry.save_to(&artifact_root)?;
let agent_dir = artifact_root.join("agents").join(name);
std::fs::create_dir_all(&agent_dir)?;
let mut check = Command::new("cargo");
check.arg("check").current_dir(&target_path);
let smoke = run_command_with_timeout(&mut check, Duration::from_secs(120));
let smoke_passed = smoke.as_ref().is_some_and(|output| output.success());
let smoke_timed_out = smoke.as_ref().is_some_and(|output| output.timed_out);
if json {
println!(
"{}",
serde_json::json!({
"status": "registered",
"name": name,
"path": target_path.display().to_string(),
"contract": contract_label,
"smoke_test_passed": smoke_passed,
"smoke_test_timed_out": smoke_timed_out
})
);
} else {
println!("✅ Registered agent '{}'", name);
println!(" Path: {}", target_path.display());
println!(" Contract detected: {contract_label}");
println!(
" Smoke test (cargo check): {}",
if smoke_passed {
"PASSED"
} else if smoke_timed_out {
"TIMED OUT"
} else {
"FAILED or skipped"
}
);
println!();
println!("Next: mdx-rust doctor {}", name);
}
Ok(())
}
fn cmd_invoke(name: &str, input: Option<&str>, json: bool) -> anyhow::Result<()> {
use mdx_rust_core::registry::Registry;
use mdx_rust_core::runner::run_agent;
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd)?;
let artifact_root = cwd.join(&config.artifact_dir);
let registry = Registry::load_from(&artifact_root)?;
let agent = registry
.get(name)
.ok_or_else(|| anyhow::anyhow!("Agent '{}' not registered", name))?;
let input_value: serde_json::Value = if let Some(s) = input {
serde_json::from_str(s)?
} else {
serde_json::json!({"query": "hello from mdx-rust", "context": null})
};
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(run_agent(agent, input_value))?;
if json {
println!("{}", serde_json::to_string_pretty(&result)?);
} else {
println!("{:#?}", result);
}
Ok(())
}
fn cmd_eval(
name: Option<&str>,
dataset: Option<&str>,
spec: Option<&str>,
json: bool,
) -> anyhow::Result<()> {
let Some(name) = name else {
return cmd_workspace_eval(spec, json);
};
use mdx_rust_core::registry::Registry;
use mdx_rust_core::EvaluationDataset;
use std::path::Path;
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd)?;
let artifact_root = cwd.join(&config.artifact_dir);
let registry = Registry::load_from(&artifact_root)?;
let agent = registry
.get(name)
.ok_or_else(|| anyhow::anyhow!("Agent '{}' not registered", name))?;
let evaluation_dataset = if let Some(dataset_path) = dataset {
EvaluationDataset::load_from_path(Path::new(dataset_path))?
} else {
EvaluationDataset::synthetic_v1()
};
let dataset_hash = evaluation_dataset.content_hash();
let sample_count = evaluation_dataset.samples.len();
if json {
println!(
"{}",
serde_json::json!({
"agent": name,
"path": agent.path.display().to_string(),
"dataset": dataset,
"dataset_version": evaluation_dataset.version,
"dataset_hash": dataset_hash,
"sample_count": sample_count,
"status": "loaded"
})
);
} else {
println!("Evaluating agent '{}' with dataset {:?}", name, dataset);
println!(
"Loaded {} sample(s), version {}, hash {}.",
sample_count, evaluation_dataset.version, dataset_hash
);
println!("Scored evaluation execution is not implemented yet.");
}
Ok(())
}
fn cmd_workspace_eval(spec: Option<&str>, json: bool) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let spec_path = spec
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::path::PathBuf::from(".mdx-rust/evals.json"));
let report = mdx_rust_core::run_behavior_evals(&cwd, &spec_path)?;
if json {
println!("{}", serde_json::to_string_pretty(&report)?);
} else {
println!("Behavior evals for workspace: {}", cwd.display());
println!(" Passed: {}/{} command(s)", report.passed, report.total);
for record in &report.command_records {
println!(
" [{}] {} - {}",
if record.success { "pass" } else { "fail" },
record.id,
record.command
);
if let Some(reason) = &record.failure_reason {
println!(" {}", reason);
}
}
}
Ok(())
}
fn cmd_optimize(
name: &str,
iterations: u32,
budget: &str,
review: bool,
json: bool,
) -> anyhow::Result<()> {
use mdx_rust_core::optimizer::{run_optimization, OptimizeConfig};
use mdx_rust_core::registry::Registry;
use mdx_rust_core::{HookPolicy, OptimizationBudget};
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd)?;
let artifact_root = cwd.join(&config.artifact_dir);
let registry = Registry::load_from(&artifact_root)?;
let agent = registry
.get(name)
.ok_or_else(|| anyhow::anyhow!("Agent '{}' not registered", name))?;
let budget = OptimizationBudget::from_label(budget)?;
let rt = tokio::runtime::Runtime::new()?;
let runs = if json {
let subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::ERROR)
.finish();
tracing::subscriber::with_default(subscriber, || {
rt.block_on(run_optimization(
agent,
&OptimizeConfig {
max_iterations: iterations,
candidates_per_iteration: 2,
use_llm_judge: false,
budget,
hook_policy: HookPolicy::default(),
review_before_apply: review,
quiet: true,
candidate_timeout: std::time::Duration::from_secs(300),
},
))
})?
} else {
rt.block_on(run_optimization(
agent,
&OptimizeConfig {
max_iterations: iterations,
candidates_per_iteration: 2,
use_llm_judge: false,
budget,
hook_policy: HookPolicy::default(),
review_before_apply: review,
quiet: false,
candidate_timeout: std::time::Duration::from_secs(300),
},
))?
};
if runs.iter().any(|r| r.accepted_changes > 0) {
let best_dir = artifact_root.join("agents").join(name).join("best");
let _ = std::fs::create_dir_all(&best_dir);
let src_dir = agent.path.join("src");
if src_dir.exists() {
let _ = copy_dir_recursive(&src_dir, &best_dir.join("src"));
}
if let Ok(cargo) = std::fs::read_to_string(agent.path.join("Cargo.toml")) {
let _ = std::fs::write(best_dir.join("Cargo.toml"), cargo);
}
if !json {
println!(
" ✓ Best improved version saved to .mdx-rust/agents/{}/best/",
name
);
}
}
if json {
println!("{}", serde_json::to_string_pretty(&runs)?);
} else {
println!("🚀 Optimization run for agent '{}'", name);
println!(" ({} iterations)", runs.len());
for run in &runs {
let avg = if run.scores.is_empty() {
0.0
} else {
run.scores.iter().sum::<f32>() / run.scores.len() as f32
};
println!(
" • Iteration {} | Avg: {:.2} | Validated: {} | Landed: {} | Accepted: {}",
run.iteration, avg, run.validated_changes, run.landed_changes, run.accepted_changes
);
if !run.notes.is_empty() {
println!(" → {}", run.notes);
}
if let Some(packet) = &run.audit_packet {
println!(
" Audit packet schema: {} | scope: {}",
packet.schema_version, packet.edit_scope_contract
);
}
for (i, c) in run.candidates.iter().enumerate() {
println!(
" [Candidate {}] {} — {}",
i + 1,
c.focus,
c.description
);
}
}
println!(
"\nArtifacts written under .mdx-rust/agents/{}/experiments/",
name
);
println!("Use `mdx-rust doctor {}` to inspect scope and state.", name);
}
Ok(())
}
struct ImproveCommand<'a> {
target: Option<&'a str>,
goal: Option<&'a str>,
policy: Option<&'a str>,
eval_spec: Option<&'a str>,
apply: bool,
max_files: usize,
tier: &'a str,
timeout_seconds: u64,
json: bool,
}
fn cmd_improve(args: ImproveCommand<'_>) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
let run = mdx_rust_core::run_hardening(
&cwd,
Some(&artifact_root),
&mdx_rust_core::HardeningConfig {
target: args.target.map(std::path::PathBuf::from),
policy_path: args.policy.map(std::path::PathBuf::from),
behavior_spec_path: args.eval_spec.map(std::path::PathBuf::from),
apply: args.apply,
max_files: args.max_files,
max_recipe_tier: parse_recipe_tier_number(args.tier)?,
evidence_depth: evidence_depth_for_tier(parse_recipe_tier_number(args.tier)?),
validation_timeout: std::time::Duration::from_secs(args.timeout_seconds),
},
)?;
if args.json {
let mut value = serde_json::to_value(&run)?;
if let Some(goal) = args.goal {
value["goal"] = serde_json::Value::String(goal.to_string());
}
println!("{}", serde_json::to_string_pretty(&value)?);
return Ok(());
}
println!(
"🛠️ mdx-rust improve — {}",
if args.apply { "apply" } else { "review" }
);
if let Some(target) = args.target {
println!(" Target: {}", target);
}
if let Some(goal) = args.goal {
println!(" Goal: {}", goal);
}
if let Some(eval_spec) = args.eval_spec {
println!(" Behavior eval spec: {}", eval_spec);
}
println!(" Findings: {}", run.findings.len());
println!(" Proposed changes: {}", run.changes.len());
println!(" Outcome: {:?}", run.outcome.status);
println!(" Note: {}", run.outcome.note);
if let Some(path) = &run.artifact_path {
println!(" Report: {}", path);
}
for change in &run.changes {
println!(" • {} — {}", change.file, change.description);
}
if !args.apply && !run.changes.is_empty() {
println!();
println!("Validated in isolation. Re-run with --apply to land the transaction.");
}
Ok(())
}
fn cmd_plan(
target: Option<&str>,
policy: Option<&str>,
eval_spec: Option<&str>,
max_files: usize,
json: bool,
) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
let plan = mdx_rust_core::build_refactor_plan(
&cwd,
Some(&artifact_root),
&mdx_rust_core::RefactorPlanConfig {
target: target.map(std::path::PathBuf::from),
policy_path: policy.map(std::path::PathBuf::from),
behavior_spec_path: eval_spec.map(std::path::PathBuf::from),
max_files,
},
)?;
if json {
println!("{}", serde_json::to_string_pretty(&plan)?);
return Ok(());
}
println!("🧭 mdx-rust plan — refactor review");
println!(" Plan: {}", plan.plan_id);
println!(" Root: {}", plan.root);
if let Some(target) = &plan.target {
println!(" Target: {}", target);
}
println!(" Files scanned: {}", plan.impact.files_scanned);
println!(
" Impact: risk={:?}, public_items={}, public_files={}, patchable={}",
plan.impact.risk_level,
plan.impact.public_item_count,
plan.impact.public_files,
plan.impact.patchable_hardening_changes
);
println!(
" Security: score={}, high={}, medium={}",
plan.security.score, plan.security.high, plan.security.medium
);
println!(" Candidates: {}", plan.candidates.len());
if let Some(path) = &plan.artifact_path {
println!(" Artifact: {}", path);
}
for candidate in plan.candidates.iter().take(8) {
println!(
" • [{:?}] {} ({})",
candidate.status, candidate.title, candidate.file
);
if let Some(command) = &candidate.apply_command {
println!(" apply: {}", command);
}
}
if plan.candidates.len() > 8 {
println!(
" … {} more candidate(s) in the plan artifact",
plan.candidates.len() - 8
);
}
println!();
println!("Required gates:");
for gate in &plan.required_gates {
println!(" - {}", gate);
}
Ok(())
}
fn cmd_map(
target: Option<&str>,
policy: Option<&str>,
eval_spec: Option<&str>,
max_files: usize,
json: bool,
) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
let map = mdx_rust_core::build_codebase_map(
&cwd,
Some(&artifact_root),
&mdx_rust_core::CodebaseMapConfig {
target: target.map(std::path::PathBuf::from),
policy_path: policy.map(std::path::PathBuf::from),
behavior_spec_path: eval_spec.map(std::path::PathBuf::from),
max_files,
},
)?;
if json {
println!("{}", serde_json::to_string_pretty(&map)?);
return Ok(());
}
println!("🗺️ mdx-rust map");
println!(" Map: {}", map.map_id);
println!(" Root: {}", map.root);
if let Some(target) = &map.target {
println!(" Target: {}", target);
}
println!(
" Quality: {:?} (debt score {}, security score {})",
map.quality.grade, map.quality.debt_score, map.quality.security_score
);
println!(
" Evidence: {:?} (max autonomous tier {})",
map.evidence.grade, map.evidence.max_autonomous_tier
);
println!(
" Files: {}, patchable: {}, review-only: {}, public items: {}",
map.impact.files_scanned,
map.quality.patchable_findings,
map.quality.review_only_findings,
map.quality.public_api_pressure
);
println!(" Capability gates:");
for gate in &map.capability_gates {
println!(
" - {}: {}",
gate.label,
if gate.available {
"available"
} else {
"missing"
}
);
}
println!(" Recommended actions:");
for action in &map.recommended_actions {
println!(" - {}", action);
}
if let Some(path) = &map.artifact_path {
println!(" Artifact: {}", path);
}
Ok(())
}
struct AutopilotCommand<'a> {
target: Option<&'a str>,
policy: Option<&'a str>,
eval_spec: Option<&'a str>,
apply: bool,
allow_public_api_impact: bool,
max_files: usize,
max_passes: usize,
max_candidates: usize,
tier: &'a str,
min_evidence: &'a str,
budget: Option<&'a str>,
timeout_seconds: u64,
json: bool,
}
fn cmd_autopilot(args: AutopilotCommand<'_>) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
let max_tier = parse_recipe_tier(args.tier)?;
let min_evidence = parse_evidence_grade(args.min_evidence)?;
let budget = args.budget.map(parse_budget).transpose()?;
let run = mdx_rust_core::run_autopilot(
&cwd,
Some(&artifact_root),
&mdx_rust_core::AutopilotConfig {
target: args.target.map(std::path::PathBuf::from),
policy_path: args.policy.map(std::path::PathBuf::from),
behavior_spec_path: args.eval_spec.map(std::path::PathBuf::from),
apply: args.apply,
max_files: args.max_files,
max_passes: args.max_passes,
max_candidates: args.max_candidates,
validation_timeout: std::time::Duration::from_secs(args.timeout_seconds),
allow_public_api_impact: args.allow_public_api_impact,
max_tier,
min_evidence,
budget,
},
)?;
if args.json {
println!("{}", serde_json::to_string_pretty(&run)?);
return Ok(());
}
println!(
"🚀 mdx-rust autopilot - {}",
if args.apply { "apply" } else { "review" }
);
if let Some(target) = args.target {
println!(" Target: {}", target);
}
println!(" Status: {:?}", run.status);
println!(
" Quality before: {:?} (debt score {})",
run.quality_before.grade, run.quality_before.debt_score
);
println!(
" Evidence: {:?} / {:?} (max tier {})",
run.evidence.grade, run.evidence.analysis_depth, run.evidence.max_autonomous_tier
);
if let Some(after) = &run.quality_after {
println!(
" Quality after: {:?} (debt score {})",
after.grade, after.debt_score
);
}
println!(" Passes: {}", run.passes.len());
println!(" Budget seconds: {:?}", run.budget_seconds);
println!(" Planned candidates: {}", run.total_planned_candidates);
println!(" Executed candidates: {}", run.total_executed_candidates);
println!(" Skipped candidates: {}", run.total_skipped_candidates);
println!(
" Validated/applied transactions: {}/{}",
run.execution_summary.validated_transactions, run.execution_summary.applied_transactions
);
println!(" Note: {}", run.note);
for pass in &run.passes {
println!(
" - pass {}: {:?}, executable {}",
pass.pass_index, pass.status, pass.executable_candidates
);
}
if let Some(path) = &run.artifact_path {
println!(" Report: {}", path);
}
Ok(())
}
fn parse_recipe_tier(value: &str) -> anyhow::Result<mdx_rust_core::RecipeTier> {
match value.to_ascii_lowercase().as_str() {
"1" | "tier1" => Ok(mdx_rust_core::RecipeTier::Tier1),
"2" | "tier2" => Ok(mdx_rust_core::RecipeTier::Tier2),
"3" | "tier3" => Ok(mdx_rust_core::RecipeTier::Tier3),
other => anyhow::bail!("unknown recipe tier: {other}"),
}
}
fn parse_recipe_tier_number(value: &str) -> anyhow::Result<u8> {
Ok(match parse_recipe_tier(value)? {
mdx_rust_core::RecipeTier::Tier1 => 1,
mdx_rust_core::RecipeTier::Tier2 => 2,
mdx_rust_core::RecipeTier::Tier3 => 3,
})
}
fn evidence_depth_for_tier(tier: u8) -> mdx_rust_core::HardeningEvidenceDepth {
match tier {
0 | 1 => mdx_rust_core::HardeningEvidenceDepth::Basic,
2 => mdx_rust_core::HardeningEvidenceDepth::Covered,
_ => mdx_rust_core::HardeningEvidenceDepth::Hardened,
}
}
fn parse_evidence_grade(value: &str) -> anyhow::Result<mdx_rust_core::EvidenceGrade> {
match value.to_ascii_lowercase().as_str() {
"none" => Ok(mdx_rust_core::EvidenceGrade::None),
"compiled" => Ok(mdx_rust_core::EvidenceGrade::Compiled),
"tested" => Ok(mdx_rust_core::EvidenceGrade::Tested),
"covered" => Ok(mdx_rust_core::EvidenceGrade::Covered),
"hardened" => Ok(mdx_rust_core::EvidenceGrade::Hardened),
"proven" => Ok(mdx_rust_core::EvidenceGrade::Proven),
other => anyhow::bail!("unknown evidence grade: {other}"),
}
}
fn parse_budget(value: &str) -> anyhow::Result<std::time::Duration> {
let trimmed = value.trim();
if trimmed.is_empty() {
anyhow::bail!("budget cannot be empty");
}
let (number, multiplier) = if let Some(number) = trimmed.strip_suffix('m') {
(number, 60)
} else if let Some(number) = trimmed.strip_suffix("min") {
(number, 60)
} else if let Some(number) = trimmed.strip_suffix('s') {
(number, 1)
} else {
(trimmed, 60)
};
let amount: u64 = number
.parse()
.map_err(|_| anyhow::anyhow!("invalid budget value: {value}"))?;
if amount == 0 {
anyhow::bail!("budget must be greater than zero");
}
Ok(std::time::Duration::from_secs(
amount.saturating_mul(multiplier),
))
}
fn max_passes_for_budget(budget: std::time::Duration) -> usize {
let minutes = budget.as_secs().div_ceil(60);
minutes.clamp(1, 6) as usize
}
struct ApplyPlanCommand<'a> {
plan_path: &'a str,
candidate_id: Option<&'a str>,
all: bool,
apply: bool,
allow_public_api_impact: bool,
timeout_seconds: u64,
max_candidates: usize,
json: bool,
}
fn cmd_apply_plan(args: ApplyPlanCommand<'_>) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
if args.all {
let run = mdx_rust_core::apply_refactor_plan_batch(
&cwd,
Some(&artifact_root),
&mdx_rust_core::RefactorBatchApplyConfig {
plan_path: std::path::PathBuf::from(args.plan_path),
apply: args.apply,
allow_public_api_impact: args.allow_public_api_impact,
validation_timeout: std::time::Duration::from_secs(args.timeout_seconds),
max_candidates: args.max_candidates,
max_tier: mdx_rust_core::RecipeTier::Tier1,
min_evidence: mdx_rust_core::EvidenceGrade::Compiled,
},
)?;
if args.json {
println!("{}", serde_json::to_string_pretty(&run)?);
return Ok(());
}
println!(
"🧰 mdx-rust apply-plan --all — {}",
if args.apply { "apply" } else { "review" }
);
println!(" Plan: {}", run.plan_id);
println!(" Status: {:?}", run.status);
println!(" Requested candidates: {}", run.requested_candidates);
println!(" Executed candidates: {}", run.executed_candidates);
println!(" Skipped candidates: {}", run.skipped_candidates);
println!(" Note: {}", run.note);
for step in &run.steps {
println!(" - {}: {:?}", step.candidate_id, step.status);
if !step.note.is_empty() {
println!(" {}", step.note);
}
}
if let Some(path) = &run.artifact_path {
println!(" Report: {}", path);
}
return Ok(());
}
let Some(candidate_id) = args.candidate_id else {
anyhow::bail!("pass --candidate <id> or --all");
};
let run = mdx_rust_core::apply_refactor_plan_candidate(
&cwd,
Some(&artifact_root),
&mdx_rust_core::RefactorApplyConfig {
plan_path: std::path::PathBuf::from(args.plan_path),
candidate_id: candidate_id.to_string(),
apply: args.apply,
allow_public_api_impact: args.allow_public_api_impact,
validation_timeout: std::time::Duration::from_secs(args.timeout_seconds),
},
)?;
if args.json {
println!("{}", serde_json::to_string_pretty(&run)?);
return Ok(());
}
println!(
"🧰 mdx-rust apply-plan — {}",
if args.apply { "apply" } else { "review" }
);
println!(" Plan: {}", run.plan_id);
println!(" Candidate: {}", run.candidate_id);
println!(" Status: {:?}", run.status);
println!(" Note: {}", run.note);
if !run.stale_files.is_empty() {
println!(" Stale files:");
for stale in &run.stale_files {
println!(
" - {} expected {} but found {}",
stale.file, stale.expected_hash, stale.actual_hash
);
}
}
if let Some(hardening) = &run.hardening_run {
println!(" Hardening status: {:?}", hardening.outcome.status);
println!(" Proposed changes: {}", hardening.changes.len());
println!(" Applied: {}", hardening.outcome.applied);
}
if let Some(path) = &run.artifact_path {
println!(" Report: {}", path);
}
Ok(())
}
fn cmd_audit(name: Option<&str>, policy: Option<&str>, json: bool) -> anyhow::Result<()> {
let Some(name) = name else {
return cmd_workspace_audit(policy, json);
};
use mdx_rust_core::registry::Registry;
use mdx_rust_core::security::audit_agent;
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd)?;
let artifact_root = cwd.join(&config.artifact_dir);
let registry = Registry::load_from(&artifact_root)?;
let agent = registry
.get(name)
.ok_or_else(|| anyhow::anyhow!("Agent '{}' not registered", name))?;
let report = audit_agent(&agent.path)?;
if json {
println!("{}", serde_json::to_string_pretty(&report)?);
} else {
println!("Security audit for '{}': {}", name, report.summary());
for finding in &report.findings {
let location = finding
.file
.as_ref()
.map(|file| {
finding
.line
.map(|line| format!("{file}:{line}"))
.unwrap_or_else(|| file.clone())
})
.unwrap_or_else(|| "workspace".to_string());
println!(
" [{:?}] {} - {} ({})",
finding.severity, finding.id, finding.title, location
);
}
}
Ok(())
}
fn cmd_workspace_audit(policy: Option<&str>, json: bool) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd).unwrap_or_default();
let artifact_root = cwd.join(&config.artifact_dir);
let run = mdx_rust_core::run_hardening(
&cwd,
Some(&artifact_root),
&mdx_rust_core::HardeningConfig {
policy_path: policy.map(std::path::PathBuf::from),
apply: false,
max_files: 100,
validation_timeout: std::time::Duration::from_secs(180),
..mdx_rust_core::HardeningConfig::default()
},
)?;
if json {
println!("{}", serde_json::to_string_pretty(&run)?);
} else {
println!("Hardening audit for workspace: {}", run.root);
println!(" Findings: {}", run.findings.len());
println!(" Patchable changes: {}", run.changes.len());
for finding in &run.findings {
println!(
" [{}] {} - {} ({}:{})",
if finding.patchable {
"patchable"
} else {
"review"
},
finding.id,
finding.title,
finding.file.display(),
finding.line
);
}
}
Ok(())
}
fn cmd_schema(kind: &str, json: bool) -> anyhow::Result<()> {
let schema = match kind {
"agent-contract" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::MdxAgentContract))?
}
"artifact-explanation" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::ArtifactExplanation))?
}
"audit-packet" => serde_json::to_value(schemars::schema_for!(mdx_rust_core::AuditPacket))?,
"candidate" => serde_json::to_value(schemars::schema_for!(mdx_rust_core::Candidate))?,
"optimization-run" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::OptimizationRun))?
}
"hook-decision" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::HookDecision))?
}
"trace-event" => serde_json::to_value(schemars::schema_for!(mdx_rust_core::TraceEvent))?,
"hardening-run" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::HardeningRun))?
}
"hardening-finding" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_analysis::HardeningFinding))?
}
"behavior-eval-report" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::BehaviorEvalReport))?
}
"project-policy" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::ProjectPolicy))?
}
"evidence-run" => serde_json::to_value(schemars::schema_for!(mdx_rust_core::EvidenceRun))?,
"recipe-catalog" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::RecipeCatalog))?
}
"evolution-scorecard" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::EvolutionScorecard))?
}
"refactor-plan" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::RefactorPlan))?
}
"refactor-apply-run" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::RefactorApplyRun))?
}
"refactor-batch-apply-run" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::RefactorBatchApplyRun))?
}
"codebase-map" => serde_json::to_value(schemars::schema_for!(mdx_rust_core::CodebaseMap))?,
"autopilot-run" => {
serde_json::to_value(schemars::schema_for!(mdx_rust_core::AutopilotRun))?
}
other => anyhow::bail!("unknown schema kind: {other}"),
};
if json {
println!("{}", serde_json::to_string_pretty(&schema)?);
} else {
println!("JSON Schema for mdx-rust {kind}:");
println!("{}", serde_json::to_string_pretty(&schema)?);
}
Ok(())
}
fn cmd_spec(name: &str, json: bool) -> anyhow::Result<()> {
use mdx_rust_core::registry::Registry;
let cwd = std::env::current_dir()?;
let config = Config::load_from_project(&cwd)?;
let artifact_root = cwd.join(&config.artifact_dir);
let registry = Registry::load_from(&artifact_root)?;
let agent = registry.get(name).ok_or_else(|| {
anyhow::anyhow!(
"Agent '{}' not registered. Run `mdx-rust register {}` first.",
name,
name
)
})?;
let agent_dir = artifact_root.join("agents").join(name);
std::fs::create_dir_all(&agent_dir)?;
let bundle = mdx_rust_analysis::analyze_agent(&agent.path, None).ok();
let analysis_summary = if let Some(b) = &bundle {
format!(
"{} files, Rig={}, preambles={:?}",
b.scope.optimizable_paths.len(),
b.is_rig_agent,
b.preambles.iter().map(|p| &p.text).collect::<Vec<_>>()
)
} else {
"limited analysis".into()
};
let policies = format!("# Policies for {}\n\n- Use explicit step-by-step reasoning in prompts.\n- Be concise but complete.\n- Structured output (answer + reasoning).\n\n(Generated from: {})", name, analysis_summary);
std::fs::write(agent_dir.join("policies.md"), policies)?;
let spec = serde_json::json!({"description": format!("Eval spec for {}", name), "dataset": "dataset.json"});
std::fs::write(
agent_dir.join("eval_spec.json"),
serde_json::to_string_pretty(&spec)?,
)?;
let ds = serde_json::json!([{"query":"What is 2+2?"},{"query":"Explain the sky being blue."}]);
std::fs::write(
agent_dir.join("dataset.json"),
serde_json::to_string_pretty(&ds)?,
)?;
if json {
println!(
"{}",
serde_json::json!({"agent":name,"policies":"policies.md","eval_spec":"eval_spec.json","dataset":"dataset.json"})
);
} else {
println!("✅ Spec generated for '{}'\n • policies.md\n • eval_spec.json\n • dataset.json\n Analysis: {}", name, analysis_summary);
}
Ok(())
}
fn detect_contract(path: &std::path::Path) -> mdx_rust_core::registry::AgentContract {
use mdx_rust_analysis::finders::find_run_agent_functions;
let cargo_content = std::fs::read_to_string(path.join("Cargo.toml")).unwrap_or_default();
if cargo_content.contains("rig-core") || cargo_content.contains("rig") {
return mdx_rust_core::registry::AgentContract::NativeRust;
}
if let Ok(main_rs) = std::fs::read_to_string(path.join("src/main.rs")) {
let found = find_run_agent_functions(&main_rs);
if !found.is_empty() || mdx_rust_analysis::finders::looks_like_rig_agent(&main_rs) {
return mdx_rust_core::registry::AgentContract::NativeRust;
}
}
mdx_rust_core::registry::AgentContract::Process
}
fn copy_dir_recursive(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if ty.is_dir() {
let name = entry.file_name();
if name == "target" || name == ".git" || name == ".worktrees" {
continue;
}
copy_dir_recursive(&src_path, &dst_path)?;
} else {
std::fs::copy(&src_path, &dst_path)?;
}
}
Ok(())
}