govctl 0.9.1

Project governance CLI for RFC, ADR, and Work Item management
const RFC_METADATA_KEYS: &[&str] = &[
    "title",
    "version",
    "status",
    "phase",
    "owners",
    "created",
    "updated",
    "supersedes",
    "refs",
    "signature",
];

const CLAUSE_METADATA_KEYS: &[&str] = &[
    "title",
    "kind",
    "status",
    "anchors",
    "superseded_by",
    "since",
];

fn move_toml_keys(
    source: &mut toml::map::Map<String, toml::Value>,
    target: &mut toml::map::Map<String, toml::Value>,
    keys: &[&str],
) {
    for key in keys {
        if let Some(v) = source.remove(*key) {
            target.insert(key.to_string(), v);
        }
    }
}

fn extract_toml_govctl(
    root: &mut toml::map::Map<String, toml::Value>,
    id_key: &str,
    metadata_keys: &[&str],
) -> Option<toml::Value> {
    if root.contains_key("govctl") {
        return None;
    }
    let id = root.remove(id_key)?;

    let mut govctl = toml::map::Map::new();
    govctl.insert("schema".to_string(), toml::Value::Integer(1));
    govctl.insert("id".to_string(), id);
    move_toml_keys(root, &mut govctl, metadata_keys);
    Some(toml::Value::Table(govctl))
}

/// Normalize a flat RFC TOML value into the `[govctl]` wire layout.
/// If the value already has a `govctl` key, it's left untouched.
pub fn normalize_rfc_value(raw: &mut toml::Value) {
    let Some(root) = raw.as_table_mut() else {
        return;
    };
    if let Some(govctl) = extract_toml_govctl(root, "rfc_id", RFC_METADATA_KEYS) {
        root.insert("govctl".to_string(), govctl);
    }
}

/// Normalize a flat clause TOML value into the `[govctl]` + `[content]` wire layout.
/// If the value already has a `govctl` key, it's left untouched.
pub fn normalize_clause_value(raw: &mut toml::Value) {
    let Some(root) = raw.as_table_mut() else {
        return;
    };
    let Some(govctl) = extract_toml_govctl(root, "clause_id", CLAUSE_METADATA_KEYS) else {
        return;
    };
    root.insert("govctl".to_string(), govctl);

    let mut content = toml::map::Map::new();
    if let Some(text) = root.remove("text") {
        content.insert("text".to_string(), text);
    }
    root.insert("content".to_string(), toml::Value::Table(content));
}