feat(TASK-038): add hooks builtin-asset category with example scripts and roles mixin scanning

- AssetCategory::Hooks + InstallFilter::Hooks (NAMES/parse/clap), reserving
  the 'hooks' bundle name via the existing AssetCategory::parse check
- HookAssets embed + install_builtin_hooks(force): installs the bundled
  example scripts 0755 into paths::hooks_dir() via write_file_atomic, wired
  into install_builtins() and install_assets(); warns once when
  COYOTE_HOOKS_DIR diverges from the config-dir default
- assets/hooks/notify.sh: notify-send -> osascript -> terminal fallback,
  with AppleScript-literal sanitization of model-influenced values
- assets/hooks/log-events.sh: appends a COYOTE_* env snapshot (secrets
  excluded, 0600 log, symlink-refusing) to ${COYOTE_HOOK_LOG:-/tmp/coyote-hooks.log}
- sbx-mixin discovery now scans roles/*.sbx-mixin.yaml and
  roles/hooks/*.sbx-mixin.yaml (mirroring the role-hook cwd)
- tests: install+0755+force, install_assets(Hooks) end-to-end, discover()
  roles scanning, reserved bundle name, no-ARGC-BUILD embed content, script
  content order + real execution (secret exclusion, tty-less fallback),
  CLI parser round-trips; hooks_dir env test made #[serial]
This commit is contained in:
Dark-Alex-17 committed 2026-09-16 13:44:35 -06:00
1 parent 0c6b7c8594
commit fc3bb259cb
9 files changed
+439 -4

No files matched your search

+1 -1
View File
@@ -184,7 +184,6 @@ pub fn role_file(name: &str) -> PathBuf {
roles_dir().join(format!("{name}.md"))
}
#[allow(dead_code)]
pub fn hooks_dir() -> PathBuf {
match env::var(get_env_name("hooks_dir")) {
Ok(value) => PathBuf::from(value),
@@ -956,6 +955,7 @@ mod tests {
}
#[test]
#[serial_test::serial]
fn hooks_dir_reflects_env_var_state() {
let env_name = get_env_name("hooks_dir");
let prev = env::var_os(&env_name);