fix: validate hook config entries partially

Hook config now supports the Claude Code structured hook format with
partial validation. Invalid hook entries are recorded in invalid_hooks
while valid siblings are retained, following the same pattern as MCP
partial validation (#440).

Key changes:
- RuntimeInvalidHookConfig now includes typed kind field (invalid_hooks_config
  or unknown_hook_event) for machine-readable error classification
- Hook parsing collects all invalid entries instead of halting at first error
- Unknown hook event names recorded as invalid without rejecting valid hooks
- Legacy bare-string hooks still load with deprecation warnings
- Claude Code documented format loads without error (matcher + nested hooks)
- config/status/doctor JSON surfaces hook_validation metadata
- classify_error_kind maps hook errors to invalid_hooks_config

Generated with https://github.com/Yeachan-Heo/gajae-code
Co-authored-by: Gajae Code <dev@gajae-code.com>
This commit is contained in:
bellman
2026-06-04 23:42:58 +09:00
parent 4619375c14
commit 453d8945bb
8 changed files with 596 additions and 131 deletions

View File

@@ -112,6 +112,7 @@ fn assert_doctor_help_json_contract(parsed: &Value) {
assert!(checks.iter().any(|check| check == "boot preflight"));
assert!(checks.iter().any(|check| check == "memory"));
assert!(checks.iter().any(|check| check == "mcp validation"));
assert!(checks.iter().any(|check| check == "hook validation"));
}
#[test]
@@ -1459,7 +1460,7 @@ fn doctor_and_resume_status_emit_json_when_requested() {
.is_some_and(|available| available.iter().any(|name| name == "web_fetch")));
let checks = doctor["checks"].as_array().expect("doctor checks");
assert_eq!(checks.len(), 10);
assert_eq!(checks.len(), 11);
let check_names = checks
.iter()
.map(|check| {
@@ -1481,6 +1482,7 @@ fn doctor_and_resume_status_emit_json_when_requested() {
"auth",
"config",
"mcp validation",
"hook validation",
"install source",
"workspace",
"memory",