//! Guaranteed, deterministic documentation output for hive-mind runs. //! //! Because cycles/directives are entirely Core-Intelligence-authored (see //! `app::workflow::hive_mind`), it could in principle never plan a "write //! docs" node for a given task. Durable documentation can't depend on that //! choice, so this step is plain Rust — not an LLM call, not a cycle the //! Core Intelligence can omit or reshape — and always runs after any //! hive-mind convergence completes. use crate::app::workflow::hive_mind::NodeReport; use crate::model::memory::Memory; use std::fmt::Write as _; use std::path::{Path, PathBuf}; /// Write a markdown report of one hive-mind convergence to /// `/docs/runs/-.md`. /// /// Flow: build a slug from the user request → format every `NodeReport` /// (grouped by cycle) with its complete output (no truncation — this is /// the durable record of what the hive actually decided and did) → append /// the final reconciled `consensus` as its own section → create /// `docs/runs/` if missing → write the file. /// /// Return: the path written, so callers can log/reference it. pub fn write_hive_mind_convergence( workspace_root: &Path, user_request: &str, reports: &[NodeReport], consensus: &str, ) -> anyhow::Result { let runs_dir = workspace_root.join("docs").join("runs"); std::fs::create_dir_all(&runs_dir)?; let ts = chrono::Utc::now(); let slug = Memory::slugify(user_request).unwrap_or_else(|| "run".to_string()); let filename = format!("{}-{}.md", ts.format("%Y%m%d-%H%M%S"), slug); let path = runs_dir.join(filename); let content = render_report(user_request, ts.timestamp_millis(), reports, consensus); std::fs::write(&path, content)?; Ok(path) } /// Render a hive-mind convergence as a markdown document. fn render_report( user_request: &str, ts_millis: i64, reports: &[NodeReport], consensus: &str, ) -> String { let mut out = String::new(); let _ = writeln!(out, "# The Hive converges: {user_request}"); let _ = writeln!(out, "\nTimestamp (ms): {ts_millis}\n"); let cycle_count = reports .iter() .map(|r| r.cycle_index) .max() .map_or(0, |m| m + 1); for cycle_index in 0..cycle_count { let _ = writeln!(out, "## Cycle {cycle_index}\n"); for r in reports.iter().filter(|r| r.cycle_index == cycle_index) { let _ = writeln!(out, "### {}\n", r.node_id); let _ = writeln!(out, "{}\n", r.output); } } let _ = writeln!(out, "## The Hive's Verdict\n"); let _ = writeln!(out, "{consensus}\n"); out } #[cfg(test)] mod tests { use super::*; #[test] fn writes_run_file_under_docs_runs() { let tmp = std::env::temp_dir().join(format!("zesdex-docs-test-{}", uuid::Uuid::new_v4())); std::fs::create_dir_all(&tmp).unwrap(); let reports = vec![NodeReport { node_id: "Node-0-0".to_string(), cycle_index: 0, output: "found the bug".to_string(), }]; let path = write_hive_mind_convergence(&tmp, "fix the bug", &reports, "the bug is a null check") .unwrap(); assert!(path.starts_with(tmp.join("docs").join("runs"))); let content = std::fs::read_to_string(&path).unwrap(); assert!(content.contains("fix the bug")); assert!(content.contains("Node-0-0")); assert!(content.contains("found the bug")); assert!(content.contains("The Hive's Verdict")); assert!(content.contains("the bug is a null check")); std::fs::remove_dir_all(&tmp).ok(); } #[test] fn falls_back_to_generic_slug_for_unslugifiable_request() { let tmp = std::env::temp_dir().join(format!("zesdex-docs-test-{}", uuid::Uuid::new_v4())); std::fs::create_dir_all(&tmp).unwrap(); let path = write_hive_mind_convergence(&tmp, "???", &[], "").unwrap(); assert!(path.file_name().unwrap().to_str().unwrap().contains("run")); std::fs::remove_dir_all(&tmp).ok(); } }