use crate::args::Args;
use crate::event::{Body, State};
use crate::model::Tree;
use crate::output::outln;
pub fn check(a: &Tree, args: &Args) -> Result<i32, crate::failure::Failure> {
let mut store: Vec<String> = Vec::new();
let mut project: Vec<String> = Vec::new();
if a.broken_lines > 0 {
store.push(format!(
"{} unreadable line(s) in .vivac/events (skipped while reading)",
a.broken_lines
));
}
for d in &a.repeated_nums {
store.push(format!(
"number {} repeated: {} and {}",
d.num, d.first, d.second
));
}
for n in a.nodes_iter() {
if let Some(p) = n.parent {
if a.node_by_num(p).is_none() {
store.push(format!(
"{} points at a parent that does not exist",
n.alias()
));
}
}
let lineage = a.ancestors(n.num);
if lineage.first().is_some_and(|r| r.parent.is_some()) {
store.push(format!("{} sits in a provenance cycle", n.alias()));
}
if n.state == State::Done && !n.forced_close && !a.open_blockers(n.num).is_empty() {
let pending_count = a.open_blockers(n.num);
let aliases: Vec<String> = pending_count.iter().map(|c| c.alias()).collect();
project.push(format!(
"{} is closed with {} open condition(s): {}",
n.alias(),
pending_count.len(),
aliases.join(", ")
));
}
}
store.sort();
project.sort();
let mut gates: Vec<String> = Vec::new();
if args.has("gates") {
if let Some(store_dir) = crate::store::store_dir() {
for root in crate::registry::roots(&store_dir) {
let Ok(project_store) = crate::store::Store::open(root.clone()) else {
continue;
};
let Ok((events, _)) = project_store.read_all() else {
continue;
};
let mut nodes = 0u64;
let mut before_first_opening = 0u64;
let mut opened = false;
for e in &events {
match &e.payload {
Body::NodeCreated { .. } => {
nodes += 1;
if !opened {
before_first_opening += 1;
}
}
Body::SessionStarted { .. } => opened = true,
_ => {}
}
}
if nodes > 0 && before_first_opening == nodes {
gates.push(format!(
"{}: {} nodes written, and not one after a session ever opened",
crate::render::project_name(&root),
nodes
));
}
}
}
gates.sort();
}
let ok = store.is_empty() && project.is_empty() && gates.is_empty();
if args.has("json") {
let mut payload = serde_json::json!({
"store": store,
"project": project,
"ok": ok,
});
if args.has("gates") {
if let serde_json::Value::Object(fields) = &mut payload {
fields.insert("gates".to_string(), serde_json::json!(gates));
}
}
outln!(
"{}",
serde_json::to_string_pretty(&payload).map_err(std::io::Error::other)?
);
} else {
outln!();
if ok {
outln!(" No findings. {} nodes checked.", a.total());
outln!();
}
if !store.is_empty() {
outln!(
" STORE ({}) <- the tool is lying; it needs fixing",
store.len()
);
outln!();
for m in &store {
outln!(" {m}");
}
outln!();
}
if !project.is_empty() {
outln!(
" PROJECT ({}) <- the store is fine; the work is not",
project.len()
);
outln!();
for m in &project {
outln!(" {m}");
}
outln!();
outln!(" A false close is not repaired by editing the tree: reopen what");
outln!(" stayed open, or close it deliberately with --force.");
outln!();
}
if !gates.is_empty() {
outln!(
" GATES ({}) <- the store is fine; nothing delivers it",
gates.len()
);
outln!();
for m in &gates {
outln!(" {m}");
}
outln!();
outln!(" A tree nobody opens is a tree nobody reads. Run vivac hooks inside");
outln!(" that project and paste what it prints.");
outln!();
}
}
Ok(i32::from(!ok))
}