Files
lijiaoqiao/tools/hook_tool.py
Your Name c169d35c72 feat: add reviewer tool, lazy loading, manifest profiles, instinct learning, and hook profiles
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)
2026-04-14 22:40:56 +08:00

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="🪝",
)