yoagent-state 0.4.2

Durable state and lineage for long-running agents.
Documentation
use crate::{ArtifactRef, NodeId, PatchId, ProjectRef, StateOp};
use serde_json::json;
use std::path::Path;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChangedFile {
    pub path: String,
    pub status: String,
}

pub fn parse_git_name_status(raw: &str) -> Vec<ChangedFile> {
    raw.lines()
        .filter_map(|line| {
            let mut parts = line.split_whitespace();
            let status = parts.next()?;
            let path = parts.next()?;
            Some(ChangedFile {
                path: path.to_string(),
                status: status.to_string(),
            })
        })
        .collect()
}

pub fn diff_artifact(
    diff_path: impl AsRef<Path>,
    summary: impl Into<String>,
    base_commit: impl Into<String>,
    files: &[ChangedFile],
) -> ArtifactRef {
    ArtifactRef::new(
        "git.diff",
        format!("file://{}", diff_path.as_ref().display()),
    )
    .with_summary(summary)
    .with_metadata(json!({
        "base_commit": base_commit.into(),
        "files_changed": files.iter().map(|file| &file.path).collect::<Vec<_>>(),
    }))
}

pub fn project_ref(
    repo: impl Into<String>,
    branch: Option<String>,
    commit: Option<String>,
    worktree: Option<String>,
) -> ProjectRef {
    ProjectRef {
        repo: repo.into(),
        branch,
        commit,
        worktree,
    }
}

pub fn changed_file_ops(patch_id: PatchId, files: &[ChangedFile]) -> Vec<StateOp> {
    let patch_node_id = NodeId::new(patch_id.0);
    let mut ops = Vec::new();
    for file in files {
        let file_id = NodeId::new(format!("file:{}", file.path));
        ops.push(StateOp::CreateNode {
            id: file_id.clone(),
            kind: "file".to_string(),
            props: json!({ "path": file.path, "status": file.status }),
        });
        ops.push(StateOp::CreateRelation {
            from: patch_node_id.clone(),
            rel: "modifies".to_string(),
            to: file_id,
            props: json!({ "status": file.status }),
        });
    }
    ops
}