pleme-doc-gen 0.1.2

Rust replacement for the M0 Python _gen-patterns.py + _gen-docs.py scripts in pleme-io/actions. Walks every action.yml + emits substrate's patterns-full.nix + per-action README.md + root catalog. Per the NO-SHELL prime directive.
//! patterns.nix emitter — produces substrate/lib/release/patterns-full.nix
//! from a scanned set of actions.

use crate::Action;
use std::collections::BTreeMap;
use std::fmt::Write;

pub fn emit(actions: &[Action]) -> String {
    // Group by category
    let mut by_cat: BTreeMap<&str, Vec<&Action>> = BTreeMap::new();
    for a in actions {
        by_cat.entry(a.category.as_str()).or_default().push(a);
    }

    let mut out = String::new();
    let _ = writeln!(out, "# auto-generated from pleme-io/actions/*/action.yml");
    let _ = writeln!(out, "# regenerate: pleme-doc-gen --actions-dir <repo> patterns > patterns-full.nix");
    let _ = writeln!(out, "# See substrate/docs/INTERLOCK.md for the vision.");
    let _ = writeln!(out);
    let _ = writeln!(out, "{{");
    for (cat, group) in &by_cat {
        let _ = writeln!(out, "  {cat} = {{");
        for a in group {
            let role = a.description.replace('\\', "\\\\").replace('"', "\\\"");
            let _ = writeln!(out, "    \"{}\" = {{", a.name);
            let _ = writeln!(out, "      uses = \"pleme-io/actions/{}@main\";", a.name);
            let _ = writeln!(out, "      backend = \"{}\";", a.backend);
            let _ = writeln!(out, "      role = \"{role}\";");
            let _ = writeln!(out, "    }};");
        }
        let _ = writeln!(out, "  }};");
    }
    let _ = writeln!(out, "}}");
    out
}