use usage_rs::spec::Effect::{self as SpecCommandEffect, Destructive, Read, Write};
pub const EFFECTS: &[(&str, SpecCommandEffect)] = &[
("activate", Read),
("api-schema", Read),
("boot", Read),
("boot disable", Write),
("boot enable", Write),
("boot status", Read),
("cd", Read),
("clean", Write),
("completion", Read),
("config", Read),
("config list", Read),
("config add", Write),
("config remove", Destructive),
("daemons", Read),
("daemons add", Write),
("daemons remove", Destructive),
("disable", Write),
("enable", Write),
("list", Read),
("log-sink", Write),
("logs", Read),
("project", Read),
("project enter", Write),
("project leave", Write),
("project list", Read),
("proxy", Read),
("proxy add", Write),
("proxy remove", Destructive),
("proxy doctor", Read),
("proxy setup", Write),
("proxy status", Read),
("proxy trust", Write),
("proxy untrust", Write),
("schema", Read),
("settings", Read),
("settings explain", Read),
("settings get", Read),
("settings list", Read),
("settings set", Write),
("sponsors", Read),
("status", Read),
("stop", Write),
("supervisor", Read),
("supervisor status", Read),
("supervisor stop", Write),
("usage", Read),
("wait", Read),
];
#[cfg(test)]
pub const UNCLASSIFIED: &[(&str, &str)] = &[
("mcp", "serves tools that start and stop daemons on request"),
("restart", "stops then reruns a user-configured daemon"),
("run", "runs a one-off command as a daemon"),
("start", "runs a user-configured daemon"),
("supervisor run", "supervises user-configured daemons"),
("supervisor start", "supervises user-configured daemons"),
(
"tui",
"an interactive dashboard that can start and stop daemons",
),
];
#[cfg(test)]
pub const FLAG_EFFECTS: &[(&str, &str, SpecCommandEffect)] = &[
("logs", "clear", Destructive),
];
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::Cli;
use std::collections::HashSet;
fn all_commands() -> Vec<String> {
let mut out = vec![];
collect(Cli::spec().root, &mut vec![], &mut out);
out
}
fn collect(
cmd: &usage_rs::spec::CommandMeta<'_>,
path: &mut Vec<String>,
out: &mut Vec<String>,
) {
for sub in cmd.subcommands {
path.push(sub.cmd.name.to_string());
out.push(path.join(" "));
collect(sub, path, out);
path.pop();
}
}
fn find_command(path: &str) -> &'static usage_rs::spec::CommandMeta<'static> {
path.split_ascii_whitespace()
.fold(Cli::spec().root, |cmd, name| {
cmd.subcommands
.iter()
.copied()
.find(|sub| sub.cmd.name == name)
.unwrap_or_else(|| panic!("no `pitchfork {path}`"))
})
}
fn classified() -> HashSet<&'static str> {
EFFECTS
.iter()
.map(|(name, _)| *name)
.chain(UNCLASSIFIED.iter().map(|(name, _)| *name))
.collect()
}
#[test]
fn overlays_annotate_commands_and_flags() {
let overlays: Vec<_> = EFFECTS
.iter()
.map(|(path, effect)| usage_rs::spec::CommandOverlay::effect(path, *effect))
.collect();
let kdl = Cli::spec().view().overlay(&overlays).to_kdl();
let line = |command: &str| {
kdl.lines()
.find(|line| line.trim_start().starts_with(&format!("cmd {command} ")))
.unwrap_or_else(|| panic!("no `pitchfork {command}` in:\n{kdl}"))
};
assert!(line("logs").contains("effect=read"), "{kdl}");
assert!(line("stop").contains("effect=write"), "{kdl}");
assert!(kdl.contains("effect=destructive"), "{kdl}");
}
#[test]
fn every_command_is_classified() {
let known = classified();
let missing: Vec<String> = all_commands()
.into_iter()
.filter(|cmd| !known.contains(cmd.as_str()))
.collect();
assert!(
missing.is_empty(),
"these commands have no entry in EFFECTS or UNCLASSIFIED \
(src/cli/command_effects.rs) — decide whether each is read, write, \
destructive, or genuinely unclassifiable:\n {}",
missing.join("\n ")
);
}
#[test]
fn no_classification_refers_to_a_missing_command() {
let present: HashSet<String> = all_commands().into_iter().collect();
let stale: Vec<&str> = classified()
.into_iter()
.filter(|name| !present.contains(*name))
.collect();
assert!(
stale.is_empty(),
"these entries no longer match a command:\n {}",
stale.join("\n ")
);
}
#[test]
fn every_flag_effect_matches_a_real_flag() {
let mut missing = vec![];
for (cmd_path, flag_name, _) in FLAG_EFFECTS {
if !find_command(cmd_path)
.flags
.iter()
.any(|flag| flag.flag.name == *flag_name)
{
missing.push(format!("{cmd_path} --{flag_name}"));
}
}
assert!(
missing.is_empty(),
"these FLAG_EFFECTS entries do not match a real flag:\n {}",
missing.join("\n ")
);
}
#[test]
fn classifications_are_not_duplicated() {
let mut seen = HashSet::new();
for name in EFFECTS
.iter()
.map(|(n, _)| *n)
.chain(UNCLASSIFIED.iter().map(|(n, _)| *n))
{
assert!(seen.insert(name), "{name} is classified twice");
}
}
}