Skip to main content

memstead_cli/commands/
pipeline.rs

1//! `memstead pipeline migrate` — convert the legacy `scopes|projections|
2//! ingests/` JSON folders at the workspace root into the four-primitive
3//! workspace-store shape under `.memstead/` (Medium / Facet / Projection /
4//! Ingest). The conversion core lives in `memstead_base::pipeline_migrate`;
5//! this is the operator-facing surface.
6
7use clap::{Args as ClapArgs, Subcommand};
8use serde_json::json;
9
10use crate::CliError;
11use crate::output::{ExitKind, print_json, print_markdown};
12use crate::setup::CliContext;
13
14#[derive(ClapArgs, Debug)]
15pub struct Args {
16    #[command(subcommand)]
17    pub command: PipelineCommand,
18}
19
20#[derive(Subcommand, Debug)]
21pub enum PipelineCommand {
22    /// Migrate the legacy `scopes|projections|ingests/` JSON folders at the
23    /// workspace root into the four-primitive workspace-store shape under
24    /// `.memstead/`. A legacy scope splits into a Medium (territory) and a
25    /// Facet (engagement). Idempotent — re-running reproduces identical
26    /// files. The legacy folders are left in place; remove them when ready.
27    Migrate,
28}
29
30pub fn run(ctx: &CliContext, args: Args) -> anyhow::Result<()> {
31    match args.command {
32        PipelineCommand::Migrate => migrate(ctx),
33    }
34}
35
36fn migrate(ctx: &CliContext) -> anyhow::Result<()> {
37    let (_shape, root) = ctx.workspace_shape().ok_or_else(|| {
38        CliError::new(
39            ExitKind::Generic,
40            "NO_WORKSPACE",
41            "not inside a Memstead workspace (no `.memstead/workspace.toml` in any ancestor)"
42                .to_string(),
43        )
44    })?;
45    let configs = memstead_base::migrate_legacy_pipeline(&root).map_err(|e| {
46        CliError::new(
47            ExitKind::Generic,
48            "PIPELINE_MIGRATE_FAILED",
49            format!("pipeline migration failed: {e}"),
50        )
51        .with_details(json!({ "error": e.to_string() }))
52    })?;
53    if ctx.json {
54        print_json(&json!({
55            "ok": true,
56            "mediums": configs.mediums.len(),
57            "facets": configs.facets.len(),
58            "projections": configs.projections.len(),
59            "ingests": configs.ingests.len(),
60        }))?;
61    } else {
62        print_markdown(&format!(
63            "# Pipeline migrated\n\nWrote to `.memstead/`: {} medium(s), {} facet(s), \
64             {} projection(s), {} ingest(s).\n\nThe legacy `scopes|projections|ingests/` \
65             folders were left in place — remove them when ready.\n",
66            configs.mediums.len(),
67            configs.facets.len(),
68            configs.projections.len(),
69            configs.ingests.len(),
70        ));
71    }
72    Ok(())
73}