use std::io::Read;
use std::path::PathBuf;
use aristo_core::index::VerifyLevel;
use crate::commands::show::read_index;
use crate::nudge::intents::authored_intents;
use crate::nudge::state::{NudgeState, STATE_FILENAME};
use crate::{CliResult, Workspace};
#[aristo::intent(
"The statusline is read-only and TOLERANT: on any failure — no workspace, \
an unreadable index, or nudges globally off — it prints nothing and exits \
0. The status bar re-renders on every turn, so a statusline that errored \
or wrote files would corrupt the bar or thrash the workspace on every \
keystroke. Silence is the correct degraded state.",
verify = "test",
id = "statusline_is_read_only_and_tolerant"
)]
pub(crate) fn run() -> CliResult<()> {
let start = cwd_from_stdin();
let Ok(ws) = Workspace::find(start.as_deref()) else {
return Ok(()); };
if ws.load_config().nudges.aggressiveness.is_off() {
return Ok(()); }
let Ok(index) = read_index(&ws.index_path()) else {
return Ok(());
};
let state = NudgeState::load(&ws.aristo_dir().join(STATE_FILENAME));
let intents = authored_intents(&index);
let unreviewed = state.unreviewed_count(
intents
.iter()
.map(|i| (i.id.as_str(), i.text_hash.as_str(), i.body_hash.as_str())),
);
let unverified = intents
.iter()
.filter(|i| !matches!(i.verify, VerifyLevel::Bool(false)) && !i.status.is_terminal_clean())
.count();
let tier = state.baseline.as_ref().map(|b| b.tier.clone());
if let Some(line) = statusline(unreviewed, unverified, tier.as_deref()) {
println!("{line}");
}
Ok(())
}
fn statusline(unreviewed: usize, unverified: usize, tier: Option<&str>) -> Option<String> {
let mut parts = Vec::new();
if unreviewed > 0 {
parts.push(format!("{unreviewed} review"));
}
if unverified > 0 {
parts.push(format!("{unverified} unverified"));
}
if let Some(t) = tier {
parts.push(t.to_string());
}
if parts.is_empty() {
None
} else {
Some(format!("aristo {}", parts.join(" · ")))
}
}
fn cwd_from_stdin() -> Option<PathBuf> {
let mut buf = String::new();
if std::io::stdin().read_to_string(&mut buf).is_err() || buf.trim().is_empty() {
return None;
}
let json: serde_json::Value = serde_json::from_str(&buf).ok()?;
let dir = json
.get("workspace")
.and_then(|w| w.get("current_dir"))
.and_then(|v| v.as_str())
.or_else(|| json.get("cwd").and_then(|v| v.as_str()))?;
Some(PathBuf::from(dir))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_when_nothing_to_surface() {
assert_eq!(statusline(0, 0, None), None);
}
#[test]
fn shows_backlog_and_tier_compactly() {
assert_eq!(
statusline(3, 2, Some("Apprentice")).as_deref(),
Some("aristo 3 review · 2 unverified · Apprentice")
);
}
#[test]
fn omits_zero_counts_but_keeps_tier() {
assert_eq!(
statusline(0, 0, Some("Adept")).as_deref(),
Some("aristo Adept")
);
assert_eq!(statusline(5, 0, None).as_deref(), Some("aristo 5 review"));
}
}