use crate::error::CliError;
use clap::Subcommand;
use std::collections::BTreeMap;
use std::io::Write;
use std::path::Path;
use std::process::Command;
#[derive(Subcommand, Debug, Clone)]
pub enum MonoCommands {
Publish {
#[arg(long, default_value_t = true)]
dry_run: bool,
},
Shims {
#[arg(long, default_value = "shims")]
output: String,
},
Audit,
Archive {
#[arg(long)]
execute: bool,
},
}
const PUBLISH_TIERS: &[(&str, &[&str])] = &[
("Tier 0: Zero-dep crates", &[
"aprender-contracts-macros", "aprender-gemm-codegen", "aprender-quant",
"aprender-rand", "aprender-fft", "aprender-sparse", "aprender-solve",
"aprender-tensor", "aprender-image",
]),
("Tier 1: Core compute", &[
"aprender-gpu", "aprender-compute", "aprender-cuda-edge",
"aprender-ptx-debug", "aprender-cupti", "aprender-cbtop",
"aprender-cgp", "aprender-explain",
]),
("Tier 2: Contracts + shared", &[
"aprender-contracts", "aprender-contracts-cli",
"aprender-profile-core", "aprender-profile",
]),
("Tier 3: Data & Storage", &[
"aprender-db", "aprender-graph", "aprender-rag",
"aprender-data", "aprender-distribute",
]),
("Tier 4: Visualization", &[
"aprender-present-core", "aprender-present-terminal",
"aprender-present-widgets", "aprender-present-layout",
"aprender-present-yaml", "aprender-present-cli",
"aprender-present-lib", "aprender-present-test",
"aprender-present-test-macros", "aprender-viz",
]),
("Tier 5: Test framework", &[
"aprender-test-derive", "aprender-test-js-gen",
"aprender-test-lib", "aprender-test-cli", "aprender-test-showcase",
]),
("Tier 6: Compressed memory", &[
"aprender-zram-core", "aprender-zram-adaptive",
"aprender-zram", "aprender-zram-cli", "aprender-zram-generator",
]),
("Tier 7: ML library", &[
"aprender-core", "aprender-verify", "aprender-verify-ml",
"aprender-simulate", "aprender-registry",
]),
("Tier 8: Training", &[
"aprender-train-common", "aprender-train-lora",
"aprender-train-distill", "aprender-train-inspect",
"aprender-train-shell", "aprender-train-bench",
"aprender-train-wasm", "aprender-train",
]),
("Tier 9: Serving + Orchestration", &[
"aprender-serve", "aprender-orchestrate",
]),
("Tier 10: CLI + Root", &[
"apr-cli", "aprender",
]),
];
const SHIM_MAP: &[(&str, &str, &str, &str)] = &[
("trueno", "aprender-compute", "trueno", "0.19.0"),
("trueno-gpu", "aprender-gpu", "trueno_gpu", "0.5.0"),
("trueno-quant", "aprender-quant", "trueno_quant", "0.2.0"),
("trueno-explain", "aprender-explain", "trueno_explain", "0.3.0"),
("trueno-fft", "aprender-fft", "trueno_fft", "0.2.0"),
("trueno-sparse", "aprender-sparse", "trueno_sparse", "0.2.0"),
("trueno-solve", "aprender-solve", "trueno_solve", "0.2.0"),
("trueno-rand", "aprender-rand", "trueno_rand", "0.2.0"),
("trueno-image", "aprender-image", "trueno_image", "0.2.0"),
("trueno-tensor", "aprender-tensor", "trueno_tensor", "0.2.0"),
("trueno-cuda-edge", "aprender-cuda-edge", "trueno_cuda_edge", "0.2.0"),
("trueno-db", "aprender-db", "trueno_db", "0.4.0"),
("trueno-graph", "aprender-graph", "trueno_graph", "0.2.0"),
("trueno-rag", "aprender-rag", "trueno_rag", "0.3.0"),
("trueno-viz", "aprender-viz", "trueno_viz", "0.3.0"),
("trueno-zram-core", "aprender-zram-core", "trueno_zram_core", "0.4.0"),
("trueno-zram-adaptive", "aprender-zram-adaptive", "trueno_zram_adaptive", "0.4.0"),
("cbtop", "aprender-cbtop", "cbtop", "0.2.0"),
("entrenar", "aprender-train", "entrenar", "0.8.0"),
("entrenar-common", "aprender-train-common", "entrenar_common", "0.2.0"),
("entrenar-lora", "aprender-train-lora", "entrenar_lora", "0.4.0"),
("realizar", "aprender-serve", "realizar", "0.9.0"),
("batuta", "aprender-orchestrate", "batuta", "0.8.0"),
("renacer", "aprender-profile", "renacer", "0.11.0"),
("renacer-core", "aprender-profile-core", "renacer_core", "0.2.0"),
("certeza", "aprender-verify", "certeza", "0.2.0"),
("verificar", "aprender-verify-ml", "verificar", "0.6.0"),
("simular", "aprender-simulate", "simular", "0.4.0"),
("repartir", "aprender-distribute", "repartir", "2.1.0"),
("alimentar", "aprender-data", "alimentar", "0.3.0"),
("pacha", "aprender-registry", "pacha", "0.3.0"),
("provable-contracts", "aprender-contracts", "provable_contracts", "0.4.0"),
("provable-contracts-macros", "aprender-contracts-macros", "provable_contracts_macros", "0.4.0"),
("presentar-core", "aprender-present-core", "presentar_core", "0.4.0"),
("presentar-terminal", "aprender-present-terminal", "presentar_terminal", "0.4.0"),
("jugar-probar", "aprender-test-lib", "jugar_probar", "1.1.0"),
];
const ARCHIVE_REPOS: &[(&str, &str)] = &[
("paiml/trueno", "crates/aprender-compute/"),
("paiml/entrenar", "crates/aprender-train/"),
("paiml/realizar", "crates/aprender-serve/"),
("paiml/Batuta", "crates/aprender-orchestrate/"),
("paiml/provable-contracts", "crates/aprender-contracts/"),
("paiml/presentar", "crates/aprender-present-*/"),
("paiml/renacer", "crates/aprender-profile/"),
("paiml/certeza", "crates/aprender-verify/"),
("paiml/trueno-db", "crates/aprender-db/"),
("paiml/trueno-graph", "crates/aprender-graph/"),
("paiml/trueno-rag", "crates/aprender-rag/"),
("paiml/trueno-viz", "crates/aprender-viz/"),
("paiml/trueno-zram", "crates/aprender-zram/"),
("paiml/alimentar", "crates/aprender-data/"),
("paiml/simular", "crates/aprender-simulate/"),
("paiml/verificar", "crates/aprender-verify-ml/"),
("paiml/repartir", "crates/aprender-distribute/"),
("paiml/probar", "crates/aprender-test-*/"),
("paiml/pacha", "crates/aprender-registry/"),
("paiml/batuta-common", "crates/aprender-orchestrate/"),
];
pub fn run(command: MonoCommands) -> Result<(), CliError> {
match command {
MonoCommands::Publish { dry_run } => run_publish(dry_run),
MonoCommands::Shims { output } => run_shims(&output),
MonoCommands::Audit => run_audit(),
MonoCommands::Archive { execute } => run_archive(execute),
}
}
fn run_publish(dry_run: bool) -> Result<(), CliError> {
if dry_run {
println!("=== DRY RUN — showing topological publish order ===\n");
}
let mut total = 0;
for (tier_name, crates) in PUBLISH_TIERS {
println!("--- {tier_name} ---");
for krate in *crates {
if dry_run {
let status = Command::new("cargo")
.args(["package", "-p", krate, "--allow-dirty", "--list"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
let ok = status.map_or(false, |s| s.success());
let marker = if ok { "OK" } else { "FAIL" };
println!(" [{marker}] cargo publish -p {krate}");
} else {
println!(" Publishing: {krate}");
let status = Command::new("cargo")
.args(["publish", "-p", krate, "--allow-dirty"])
.status()
.map_err(|e| CliError::Aprender(format!("cargo publish failed: {e}")))?;
if !status.success() {
return Err(CliError::Aprender(format!(
"FALSIFY-PUB-001: cargo publish -p {krate} failed (exit {})",
status.code().unwrap_or(-1)
)));
}
std::thread::sleep(std::time::Duration::from_secs(15));
}
total += 1;
}
}
println!("\n{total} crates in topological order.");
if dry_run {
println!("Run without --dry-run to publish for real.");
}
Ok(())
}
fn run_shims(output_dir: &str) -> Result<(), CliError> {
let output = Path::new(output_dir);
std::fs::create_dir_all(output)
.map_err(|e| CliError::Aprender(format!("failed to create {output_dir}: {e}")))?;
let mut count = 0;
for (old_name, new_name, lib_name, shim_version) in SHIM_MAP {
let shim_path = output.join(old_name);
std::fs::create_dir_all(shim_path.join("src"))
.map_err(|e| CliError::Aprender(format!("mkdir failed: {e}")))?;
let cargo_toml = format!(
r#"[package]
name = "{old_name}"
version = "{shim_version}"
edition = "2021"
license = "MIT"
description = "DEPRECATED: Use {new_name} instead. Re-exports {new_name} for backward compatibility."
repository = "https://github.com/paiml/aprender"
keywords = ["deprecated", "moved", "aprender"]
[dependencies]
{new_name} = "0.29"
"#
);
std::fs::write(shim_path.join("Cargo.toml"), cargo_toml)
.map_err(|e| CliError::Aprender(format!("write failed: {e}")))?;
let lib_rs = format!(
r#"//! `{old_name}` has moved to `{new_name}`.
//!
//! This crate re-exports `{new_name}` for backward compatibility.
//! New code should depend on `{new_name}` directly.
pub use {lib_name}::*;
"#
);
std::fs::write(shim_path.join("src/lib.rs"), lib_rs)
.map_err(|e| CliError::Aprender(format!("write failed: {e}")))?;
count += 1;
}
println!("Generated {count} shim crates in {output_dir}/");
println!("To publish: for d in {output_dir}/*/; do (cd \"$d\" && cargo publish); done");
Ok(())
}
fn run_audit() -> Result<(), CliError> {
println!("Running monorepo invariant checks...\n");
let output = Command::new("cargo")
.args(["metadata", "--format-version", "1", "--no-deps"])
.output()
.map_err(|e| CliError::Aprender(format!("cargo metadata failed: {e}")))?;
let stdout = String::from_utf8_lossy(&output.stdout);
let member_count = stdout.matches("\"source\":null").count();
println!("[{}] Workspace members: {member_count} (min: 60)",
if member_count >= 60 { "PASS" } else { "FAIL" });
let has_old_names = stdout.contains("\"name\":\"trueno\"")
|| stdout.contains("\"name\":\"realizar\"")
|| stdout.contains("\"name\":\"entrenar\"")
|| stdout.contains("\"name\":\"batuta\"");
println!("[{}] No old package names",
if !has_old_names { "PASS" } else { "FAIL" });
let root_toml = std::fs::read_to_string("Cargo.toml").unwrap_or_default();
let has_patch = root_toml.contains("[patch.crates-io]");
println!("[{}] No [patch.crates-io] in root Cargo.toml",
if !has_patch { "PASS" } else { "FAIL" });
println!("\nRunning integration tests...");
let test_status = Command::new("cargo")
.args(["test", "--test", "monorepo_invariants", "-p", "aprender-core"])
.status()
.map_err(|e| CliError::Aprender(format!("test failed: {e}")))?;
println!("[{}] monorepo_invariants tests",
if test_status.success() { "PASS" } else { "FAIL" });
let cli_test = Command::new("cargo")
.args(["test", "--test", "cli_commands", "-p", "apr-cli"])
.status()
.map_err(|e| CliError::Aprender(format!("test failed: {e}")))?;
println!("[{}] cli_commands tests",
if cli_test.success() { "PASS" } else { "FAIL" });
if !test_status.success() || !cli_test.success() {
return Err(CliError::Aprender("Audit failed — see test output above".into()));
}
println!("\nAll checks passed.");
Ok(())
}
fn run_archive(execute: bool) -> Result<(), CliError> {
println!("=== APR-MONO Phase 6: Archive Old Repositories ===\n");
if !execute {
println!("DRY RUN — showing commands. Use --execute to run them.\n");
}
for (repo, new_location) in ARCHIVE_REPOS {
let cmd = format!("gh api -X PATCH repos/{repo} -f archived=true");
if execute {
println!("Archiving: {repo} → {new_location}");
let status = Command::new("gh")
.args(["api", "-X", "PATCH", &format!("repos/{repo}"), "-f", "archived=true"])
.status()
.map_err(|e| CliError::Aprender(format!("gh api failed: {e}")))?;
if !status.success() {
eprintln!(" WARNING: Failed to archive {repo}");
}
} else {
println!(" {cmd}");
println!(" → {new_location}");
}
}
println!("\n{} repos to archive.", ARCHIVE_REPOS.len());
Ok(())
}