Files
zesdex/src/app/workflow/docs.rs
T

112 lines
4.0 KiB
Rust
Raw Normal View History

//! 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
/// `<workspace_root>/docs/runs/<timestamp>-<slug>.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<PathBuf> {
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();
}
}