Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion plugins/hookify/core/config_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class Rule:
action: str = "warn" # "warn" or "block" (future)
tool_matcher: Optional[str] = None # Override tool matching
message: str = "" # Message body from markdown
silent: bool = False # If true, suppress systemMessage (banner) and
# deliver the warning only via additionalContext
# (assistant-facing self-correction mode).

@classmethod
def from_dict(cls, frontmatter: Dict[str, Any], message: str) -> 'Rule':
Expand Down Expand Up @@ -80,7 +83,8 @@ def from_dict(cls, frontmatter: Dict[str, Any], message: str) -> 'Rule':
conditions=conditions,
action=frontmatter.get('action', 'warn'),
tool_matcher=frontmatter.get('tool_matcher'),
message=message.strip()
message=message.strip(),
silent=bool(frontmatter.get('silent', False)),
)


Expand Down
26 changes: 21 additions & 5 deletions plugins/hookify/core/rule_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ def evaluate_rules(self, rules: List[Rule], input_data: Dict[str, Any]) -> Dict[
return {
"hookSpecificOutput": {
"hookEventName": hook_event,
"permissionDecision": "deny"
"permissionDecision": "deny",
"permissionDecisionReason": combined_message,
},
"systemMessage": combined_message
}
Expand All @@ -85,10 +86,25 @@ def evaluate_rules(self, rules: List[Rule], input_data: Dict[str, Any]) -> Dict[

# If only warnings, show them but allow operation
if warning_rules:
messages = [f"**[{r.name}]**\n{r.message}" for r in warning_rules]
return {
"systemMessage": "\n\n".join(messages)
}
all_messages = [f"**[{r.name}]**\n{r.message}" for r in warning_rules]
visible_messages = [f"**[{r.name}]**\n{r.message}"
for r in warning_rules
if not getattr(r, 'silent', False)]
response = {}
# Silent rules suppress the user-facing banner; non-silent rules
# still show systemMessage. If ALL matching rules are silent, no
# banner is emitted at all.
if visible_messages:
response["systemMessage"] = "\n\n".join(visible_messages)
# PreToolUse additionalContext supported since Claude Code v2.1.9.
# PostToolUse has supported it since earlier. Inject so Claude
# (not just the user console) sees the educational message.
if hook_event in ('PreToolUse', 'PostToolUse'):
response["hookSpecificOutput"] = {
"hookEventName": hook_event,
"additionalContext": "\n\n".join(all_messages),
}
return response

# No matches - allow operation
return {}
Expand Down
Loading