use crate::audit::Audit;
use crate::project::Project;
use crate::runner::HelpOutput;
use crate::types::{AuditGroup, AuditLayer, AuditResult, AuditStatus, Confidence};
const TTY_PHRASES: &[&str] = &[
"when tty",
"if tty",
"non-tty",
"non tty",
"interactive terminal",
"isatty",
"is-a-tty",
"is a terminal",
"is a tty",
"color=auto",
"--color auto",
"color auto",
"pipe",
"piped",
"redirected",
"auto-detect",
"automatic verbosity",
"automatic detection",
];
pub struct AutoVerbosityAudit;
impl Audit for AutoVerbosityAudit {
fn id(&self) -> &str {
"p7-auto-verbosity"
}
fn label(&self) -> &'static str {
"Help text advertises TTY-aware verbosity behavior"
}
fn group(&self) -> AuditGroup {
AuditGroup::P7
}
fn layer(&self) -> AuditLayer {
AuditLayer::Behavioral
}
fn covers(&self) -> &'static [&'static str] {
&["p7-may-auto-verbosity"]
}
fn applicable(&self, project: &Project) -> bool {
project.runner.is_some()
}
fn run(&self, project: &Project) -> anyhow::Result<AuditResult> {
let status = match project.help_output() {
None => AuditStatus::Skip("could not probe --help".into()),
Some(help) => audit_auto_verbosity(help),
};
Ok(AuditResult {
id: self.id().to_string(),
label: self.label().into(),
group: self.group(),
layer: self.layer(),
status,
confidence: Confidence::Low,
})
}
}
pub(crate) fn audit_auto_verbosity(help: &HelpOutput) -> AuditStatus {
let lower = help.raw().to_lowercase();
let matched = TTY_PHRASES.iter().find(|phrase| lower.contains(*phrase));
match matched {
Some(_) => AuditStatus::Pass,
None => AuditStatus::Warn(
"no TTY-aware language found in `--help`. MAY-tier — automatic \
verbosity reduction when stdout is piped or redirected lets \
agents skip the explicit `--quiet` flag. Behavioral probes \
cannot simulate a real TTY without a pty crate, so this audit \
relies on documented intent."
.into(),
),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pass_when_help_mentions_tty() {
let help = HelpOutput::from_raw(
"Usage: tool [OPTIONS]\n\nProgress bars are shown when stdout is a TTY.\n",
);
assert_eq!(audit_auto_verbosity(&help), AuditStatus::Pass);
}
#[test]
fn pass_when_help_mentions_color_auto() {
let help = HelpOutput::from_raw(
"Options:\n --color <WHEN> auto-detect TTY, then enable color when supported.\n",
);
assert_eq!(audit_auto_verbosity(&help), AuditStatus::Pass);
}
#[test]
fn pass_when_help_mentions_piping() {
let help = HelpOutput::from_raw(
"Quiet mode is activated automatically when output is piped to another command.\n",
);
assert_eq!(audit_auto_verbosity(&help), AuditStatus::Pass);
}
#[test]
fn warn_when_no_tty_language() {
let help = HelpOutput::from_raw(
"Usage: tool [OPTIONS]\n\nOptions:\n -q, --quiet Be quiet.\n -h, --help Show help.\n",
);
match audit_auto_verbosity(&help) {
AuditStatus::Warn(msg) => assert!(msg.contains("TTY-aware")),
other => panic!("expected Warn, got {other:?}"),
}
}
#[test]
fn case_insensitive_match() {
let help = HelpOutput::from_raw("Behavior changes WHEN TTY is connected.\n");
assert_eq!(audit_auto_verbosity(&help), AuditStatus::Pass);
}
}