feat: add hooks inspection report

This adds a narrow, shippable /hooks surface that reports the merged\nPreToolUse and PostToolUse shell hook configuration from the Rust\nruntime. The CLI now exposes hooks consistently in direct, REPL, and\nresume-safe slash-command flows, with focused tests covering parsing,\nhelp text, and report rendering.\n\nConstraint: Keep the increment inspection-only instead of introducing a broader TS-style hook model\nRejected: Build matcher-based or interactive hook editing now | too broad for the next parity slice\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Extend /hooks from the runtime's current string-list model unless config parsing grows first\nTested: cargo fmt --all; cargo test -p commands; cargo test -p claw-cli; cargo test --workspace\nNot-tested: cargo clippy --workspace --all-targets -- -D warnings (blocked by unrelated existing lsp warnings in rust/crates/lsp/src/client.rs and rust/crates/lsp/src/lib.rs)
This commit is contained in:
Yeachan-Heo
2026-04-01 23:47:18 +00:00
parent 05bcf6751b
commit a2f22b1ece
3 changed files with 218 additions and 18 deletions

View File

@@ -22,9 +22,9 @@ use api::{
};
use commands::{
handle_agents_slash_command, handle_plugins_slash_command, handle_skills_slash_command,
render_slash_command_help, resume_supported_slash_commands, slash_command_specs,
suggest_slash_commands, SlashCommand,
handle_agents_slash_command, handle_hooks_slash_command, handle_plugins_slash_command,
handle_skills_slash_command, render_slash_command_help, resume_supported_slash_commands,
slash_command_specs, suggest_slash_commands, SlashCommand,
};
use compat_harness::{extract_manifest, UpstreamPaths};
use init::initialize_repo;
@@ -86,6 +86,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
CliAction::DumpManifests => dump_manifests(),
CliAction::BootstrapPlan => print_bootstrap_plan(),
CliAction::Agents { args } => LiveCli::print_agents(args.as_deref())?,
CliAction::Hooks { args } => LiveCli::print_hooks(args.as_deref())?,
CliAction::Skills { args } => LiveCli::print_skills(args.as_deref())?,
CliAction::PrintSystemPrompt { cwd, date } => print_system_prompt(cwd, date),
CliAction::Version => print_version(),
@@ -121,6 +122,9 @@ enum CliAction {
Agents {
args: Option<String>,
},
Hooks {
args: Option<String>,
},
Skills {
args: Option<String>,
},
@@ -290,6 +294,9 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
"agents" => Ok(CliAction::Agents {
args: join_optional_args(&rest[1..]),
}),
"hooks" => Ok(CliAction::Hooks {
args: join_optional_args(&rest[1..]),
}),
"skills" => Ok(CliAction::Skills {
args: join_optional_args(&rest[1..]),
}),
@@ -332,6 +339,7 @@ fn parse_direct_slash_cli_action(rest: &[String]) -> Result<CliAction, String> {
match SlashCommand::parse(&raw) {
Some(SlashCommand::Help) => Ok(CliAction::Help),
Some(SlashCommand::Agents { args }) => Ok(CliAction::Agents { args }),
Some(SlashCommand::Hooks { args }) => Ok(CliAction::Hooks { args }),
Some(SlashCommand::Skills { args }) => Ok(CliAction::Skills { args }),
Some(command) => Err(format_direct_slash_command_error(
match &command {
@@ -943,6 +951,13 @@ fn run_resume_command(
session: session.clone(),
message: Some(render_config_report(section.as_deref())?),
}),
SlashCommand::Hooks { args } => {
let cwd = env::current_dir()?;
Ok(ResumeCommandOutcome {
session: session.clone(),
message: Some(handle_hooks_slash_command(args.as_deref(), &cwd)?),
})
}
SlashCommand::Memory => Ok(ResumeCommandOutcome {
session: session.clone(),
message: Some(render_memory_report()?),
@@ -1295,6 +1310,10 @@ impl LiveCli {
Self::print_config(section.as_deref())?;
false
}
SlashCommand::Hooks { args } => {
Self::print_hooks(args.as_deref())?;
false
}
SlashCommand::Memory => {
Self::print_memory()?;
false
@@ -1556,6 +1575,12 @@ impl LiveCli {
Ok(())
}
fn print_hooks(args: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
let cwd = env::current_dir()?;
println!("{}", handle_hooks_slash_command(args, &cwd)?);
Ok(())
}
fn print_skills(args: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
let cwd = env::current_dir()?;
println!("{}", handle_skills_slash_command(args, &cwd)?);
@@ -4057,6 +4082,10 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
out,
" claw agents List configured agents"
)?;
writeln!(
out,
" claw hooks Inspect configured tool hooks"
)?;
writeln!(
out,
" claw skills List discoverable local skills"
@@ -4128,6 +4157,7 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
" claw --resume session.json /status /diff /export notes.txt"
)?;
writeln!(out, " claw agents")?;
writeln!(out, " claw hooks")?;
writeln!(out, " claw /skills")?;
writeln!(out, " claw login")?;
writeln!(out, " claw init")?;
@@ -4355,6 +4385,10 @@ mod tests {
parse_args(&["agents".to_string()]).expect("agents should parse"),
CliAction::Agents { args: None }
);
assert_eq!(
parse_args(&["hooks".to_string()]).expect("hooks should parse"),
CliAction::Hooks { args: None }
);
assert_eq!(
parse_args(&["skills".to_string()]).expect("skills should parse"),
CliAction::Skills { args: None }
@@ -4374,6 +4408,10 @@ mod tests {
parse_args(&["/agents".to_string()]).expect("/agents should parse"),
CliAction::Agents { args: None }
);
assert_eq!(
parse_args(&["/hooks".to_string()]).expect("/hooks should parse"),
CliAction::Hooks { args: None }
);
assert_eq!(
parse_args(&["/skills".to_string()]).expect("/skills should parse"),
CliAction::Skills { args: None }
@@ -4482,6 +4520,7 @@ mod tests {
assert!(help.contains("/cost"));
assert!(help.contains("/resume <session-path>"));
assert!(help.contains("/config [env|hooks|model|plugins]"));
assert!(help.contains("/hooks"));
assert!(help.contains("/memory"));
assert!(help.contains("/init"));
assert!(help.contains("/diff"));
@@ -4546,8 +4585,8 @@ mod tests {
assert_eq!(
names,
vec![
"help", "status", "compact", "clear", "cost", "config", "memory", "init", "diff",
"version", "export", "agents", "skills",
"help", "status", "compact", "clear", "cost", "config", "hooks", "memory", "init",
"diff", "version", "export", "agents", "skills",
]
);
}
@@ -4618,6 +4657,7 @@ mod tests {
assert!(help.contains("claw init"));
assert!(help.contains("Open slash suggestions in the REPL"));
assert!(help.contains("claw agents"));
assert!(help.contains("claw hooks"));
assert!(help.contains("claw skills"));
assert!(help.contains("claw /skills"));
}