Features: - reviewer_tool: Multi-language code review (Python, JS/TS, Go, Rust, Java, C++, C#, PHP) - lazy_loader: Context-triggered skill loading with 3-tier budget system - manifest: Profile-based skill installation (coding, debugging, planning, shipping, research) - instinct: Learning system that tracks patterns and predicts skill loading - hook_profile: Security hook profiles (minimal, standard, strict, developer, rust-dev) - hook_tool + hooks: Post-write hook execution system Code Review Tool: - Supports 9 languages with lint, typecheck, format, test, security checks - Auto-detects language from file extensions - Configurable tools per language (e.g., ruff, eslint, golangci-lint) Lazy Loading: - CONTEXT_TO_SKILLS mapping for 20+ context triggers - Budget-aware loading (Tier 0: core, Tier 1: context, Tier 2: rare) - Emergency mode at >90% context usage Integration: - Registered in model_tools.py and toolsets.py - 8 language reviewers mapped in lazy_loader (python, go, rust, js, java, cpp, csharp, php)
151 lines
5.4 KiB
Python
151 lines
5.4 KiB
Python
"""Hook management tools for Hermes.
|
|
|
|
Provides tools for listing, creating, deleting, enabling, and disabling hooks.
|
|
Hooks are YAML files in ~/.hermes/hooks/ that run actions on file/command events.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
|
|
from tools.registry import registry
|
|
|
|
# Lazy import to avoid circular imports
|
|
def _get_hooks_module():
|
|
from tools import hooks as _hooks
|
|
return _hooks
|
|
|
|
|
|
def _hooks_list(action: str = "list", hook_path: str = None) -> str:
|
|
"""Tool wrapper for hook operations."""
|
|
hooks = _get_hooks_module()
|
|
|
|
if action == "list":
|
|
result = hooks.list_hooks()
|
|
if not result:
|
|
return json.dumps({"hooks": {}, "message": "No hooks configured"})
|
|
return json.dumps({"hooks": result}, ensure_ascii=False)
|
|
|
|
elif action == "create":
|
|
if not hook_path:
|
|
return json.dumps({"error": "hook_path required for create"})
|
|
parts = hook_path.split("/", 1)
|
|
if len(parts) != 2:
|
|
return json.dumps({"error": "hook_path must be in format '<subdir>/<name>' (e.g. 'post-write/pytest-on-py')"})
|
|
subdir, name = parts
|
|
if subdir not in ("pre-write", "post-write", "pre-command", "post-command"):
|
|
return json.dumps({"error": f"Invalid subdir '{subdir}'. Must be one of: pre-write, post-write, pre-command, post-command"})
|
|
success, msg = hooks.create_hook(
|
|
name=name,
|
|
subdir=subdir,
|
|
trigger_event="file_write",
|
|
pattern="*.py",
|
|
actions=[{"type": "command", "command": "pytest {file}", "timeout": 60, "block_on_failure": True}],
|
|
description=f"Auto-created hook: {name}",
|
|
)
|
|
return json.dumps({"success": success, "message": msg}, ensure_ascii=False)
|
|
|
|
elif action == "delete":
|
|
if not hook_path:
|
|
return json.dumps({"error": "hook_path required for delete"})
|
|
success, msg = hooks.delete_hook(hook_path)
|
|
return json.dumps({"success": success, "message": msg}, ensure_ascii=False)
|
|
|
|
elif action == "enable":
|
|
if not hook_path:
|
|
return json.dumps({"error": "hook_path required for enable"})
|
|
success, msg = hooks.enable_hook(hook_path)
|
|
return json.dumps({"success": success, "message": msg}, ensure_ascii=False)
|
|
|
|
elif action == "disable":
|
|
if not hook_path:
|
|
return json.dumps({"error": "hook_path required for disable"})
|
|
success, msg = hooks.disable_hook(hook_path)
|
|
return json.dumps({"success": success, "message": msg}, ensure_ascii=False)
|
|
|
|
elif action == "dry_run":
|
|
if not hook_path:
|
|
return json.dumps({"error": "hook_path required for dry_run"})
|
|
# Dry run a specific hook
|
|
hooks_dir = hooks._get_hooks_dir()
|
|
full_path = hooks_dir / hook_path
|
|
if not full_path.exists():
|
|
return json.dumps({"error": f"Hook not found: {hook_path}"})
|
|
hook = hooks._load_hook_file(full_path)
|
|
if not hook:
|
|
return json.dumps({"error": f"Failed to load hook: {hook_path}"})
|
|
# Simulate what would happen
|
|
return json.dumps({
|
|
"hook": hook.name,
|
|
"description": hook.description,
|
|
"trigger_event": hook.trigger_event,
|
|
"trigger_pattern": hook.trigger_pattern,
|
|
"actions": [{"type": a.type, "command": a.command} for a in hook.actions],
|
|
"dry_run": True,
|
|
"note": "Commands would be shown but not executed",
|
|
}, ensure_ascii=False)
|
|
|
|
else:
|
|
return json.dumps({"error": f"Unknown action: {action}"})
|
|
|
|
|
|
# Schema for hook tool
|
|
_hooks_schema = {
|
|
"name": "hooks",
|
|
"description": """Manage Hermes hooks — trigger-based automation for file and command events.
|
|
|
|
Hooks run actions (commands, notifications) when files are written or commands are executed.
|
|
|
|
**Hook directories:**
|
|
- `pre-write/` — before file write (can block the write)
|
|
- `post-write/` — after file write
|
|
- `pre-command/` — before terminal command (can block the command)
|
|
- `post-command/` — after terminal command
|
|
|
|
**Actions:**
|
|
- `command` — run a shell command
|
|
- `notification` — log or send notification
|
|
- `log` — log a message
|
|
|
|
**Examples:**
|
|
- List all hooks: `{"action": "list"}`
|
|
- Create a Python test hook: `{"action": "create", "hook_path": "post-write/pytest-on-py"}`
|
|
- Delete a hook: `{"action": "delete", "hook_path": "post-write/pytest-on-py"}`
|
|
- Dry run a hook: `{"action": "dry_run", "hook_path": "post-write/pytest-on-py"}`""",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"action": {
|
|
"type": "string",
|
|
"enum": ["list", "create", "delete", "enable", "disable", "dry_run"],
|
|
"description": "Action to perform",
|
|
},
|
|
"hook_path": {
|
|
"type": "string",
|
|
"description": "Hook path for create/delete/enable/disable/dry_run. Format: '<subdir>/<name>' e.g. 'post-write/pytest-on-py'",
|
|
},
|
|
},
|
|
"required": ["action"],
|
|
},
|
|
}
|
|
|
|
|
|
def _check_hooks_available() -> bool:
|
|
"""Check if hooks module is available."""
|
|
return True
|
|
|
|
|
|
registry.register(
|
|
name="hooks",
|
|
toolset="file",
|
|
schema=_hooks_schema,
|
|
handler=lambda args, **kw: _hooks_list(
|
|
action=args.get("action", "list"),
|
|
hook_path=args.get("hook_path"),
|
|
),
|
|
check_fn=_check_hooks_available,
|
|
requires_env=[],
|
|
is_async=False,
|
|
description="Manage Hermes hooks for file/command automation",
|
|
emoji="🪝",
|
|
)
|