scripts-hook-expert
""" Hook Expert - Type Expert for Hook Documents
Identifies hook definitions - event-driven automation triggers that execute in response to git events, session events, or other system triggers.
Key signals:
- Hook naming patterns (pre-, post-, on-*)
- Event trigger definitions
- Execution conditions
- Script/command references
- Hook lifecycle documentation """
import re from typing import Dict, List from pathlib import Path
import sys sys.path.insert(0, str(Path(file).parent.parent)) from core.models import Document, AnalystVote
from .base import TypeExpert, TypeAnalysis, ContentEnhancement
class HookExpert(TypeExpert): """Expert for identifying hook documents."""
EXPERT_TYPE = 'hook'
# Hook-specific indicators
HOOK_PATTERNS = [
r'pre[-_]?commit',
r'post[-_]?commit',
r'pre[-_]?push',
r'post[-_]?push',
r'pre[-_]?merge',
r'post[-_]?merge',
r'pre[-_]?checkout',
r'post[-_]?checkout',
r'pre[-_]?session',
r'post[-_]?session',
r'on[-_]?error',
r'on[-_]?success',
r'on[-_]?failure',
r'hook\s*trigger',
r'event\s*handler',
r'lifecycle\s*hook',
]
HOOK_SECTIONS = [
'trigger',
'event',
'when to trigger',
'execution',
'hook configuration',
'hook script',
'activation',
]
HOOK_FRONTMATTER = [
'component_type: hook',
'type: hook',
'hook_type',
'trigger_event',
'hook_name',
]
def analyze(self, document: Document, analyst_votes: List[AnalystVote]) -> TypeAnalysis:
"""Analyze if document is a hook definition."""
content = document.content.lower()
evidence_for = []
evidence_against = []
missing_signals = []
# Check frontmatter
frontmatter_match = False
for pattern in self.HOOK_FRONTMATTER:
if pattern in content[:1000]:
evidence_for.append(f"Frontmatter indicates hook: '{pattern}'")
frontmatter_match = True
# Check filename
filename = Path(document.path).stem.lower()
if 'hook' in filename:
evidence_for.append(f"Filename contains 'hook': {filename}")
if any(p in filename for p in ['pre-', 'post-', 'on-']):
evidence_for.append(f"Filename has hook prefix pattern: {filename}")
# Check for hook patterns
hook_pattern_count = 0
for pattern in self.HOOK_PATTERNS:
if re.search(pattern, content, re.IGNORECASE):
hook_pattern_count += 1
if hook_pattern_count <= 3:
evidence_for.append(f"Contains hook pattern: '{pattern}'")
# Check for hook sections
section_count = 0
for section in self.HOOK_SECTIONS:
if re.search(rf'#+\s*{section}|^\*\*{section}\*\*', content, re.IGNORECASE | re.MULTILINE):
section_count += 1
evidence_for.append(f"Has hook section: '{section}'")
# Check for script/command references
if re.search(r'```(bash|sh|shell)', content):
evidence_for.append("Contains shell script blocks")
if re.search(r'#!/bin/(bash|sh)', content):
evidence_for.append("Contains shebang for shell script")
# Check for event-driven language
if re.search(r'(triggered|fires|executes)\s+(when|before|after|on)', content, re.IGNORECASE):
evidence_for.append("Uses event-driven language")
# Evidence against
if re.search(r'#+\s*(step\s+\d|how\s+to)', content, re.IGNORECASE):
evidence_against.append("Has step-by-step sections - might be guide")
if re.search(r'api\s*reference|schema|specification', content, re.IGNORECASE):
evidence_against.append("Has API/spec content - might be reference")
# Missing signals
if not frontmatter_match:
missing_signals.append('hook_frontmatter')
if section_count < 2:
missing_signals.append('trigger_section')
if not re.search(r'when\s+(to\s+)?(trigger|run|execute)', content, re.IGNORECASE):
missing_signals.append('trigger_conditions')
# Calculate confidence
confidence = self._calculate_confidence(
evidence_for, evidence_against, frontmatter_match, hook_pattern_count
)
is_hook = confidence > 0.6 or (frontmatter_match and confidence > 0.4)
# Determine which analysts to sway
analysts_to_sway = {}
for vote in analyst_votes:
if vote.classification != 'hook' and is_hook:
analysts_to_sway[vote.agent] = f"Document is hook, not {vote.classification}"
return TypeAnalysis(
is_this_type=is_hook,
confidence=confidence,
evidence_for=evidence_for,
evidence_against=evidence_against,
semantic_purpose="Define event-driven automation trigger" if is_hook else "Unknown",
missing_signals=missing_signals,
recommended_changes=[],
analysts_to_sway=analysts_to_sway,
expert_type=self.EXPERT_TYPE
)
def _calculate_confidence(
self,
evidence_for: List[str],
evidence_against: List[str],
frontmatter_match: bool,
pattern_count: int
) -> float:
"""Calculate confidence score."""
base = 0.3 if frontmatter_match else 0.1
# Add for evidence
base += min(0.4, len(evidence_for) * 0.08)
base += min(0.2, pattern_count * 0.05)
# Subtract for counter-evidence
base -= len(evidence_against) * 0.1
return max(0.0, min(0.98, base))
def generate_enhancements(
self,
document: Document,
analysis: TypeAnalysis
) -> List[ContentEnhancement]:
"""Generate enhancements for hook documents."""
enhancements = []
for signal in analysis.missing_signals:
if signal == 'hook_frontmatter':
enhancements.append(ContentEnhancement(
signal_type='hook_frontmatter',
content=self._generate_hook_frontmatter(document),
insertion_point='start',
reason='Hook documents need proper frontmatter with component_type: hook',
expected_analyst_boost={'metadata': 0.3, 'pattern': 0.2},
priority=1
))
elif signal == 'trigger_section':
enhancements.append(ContentEnhancement(
signal_type='trigger_section',
content=self._generate_trigger_section(document),
insertion_point='after_title',
reason='Hook documents need trigger/event documentation',
expected_analyst_boost={'structural': 0.2, 'content': 0.15},
priority=1
))
elif signal == 'trigger_conditions':
enhancements.append(ContentEnhancement(
signal_type='trigger_conditions',
content=self._generate_trigger_conditions(),
insertion_point='trigger_section',
reason='Hook documents should specify when the hook triggers',
expected_analyst_boost={'content': 0.15, 'semantic': 0.1},
priority=2
))
return enhancements
def _generate_hook_frontmatter(self, document: Document) -> str:
"""Generate hook frontmatter."""
name = Path(document.path).stem
return f'''---
title: {name.replace('-', ' ').title()} component_type: hook trigger_event: [specify event] version: 1.0.0 status: active ---'''
def _generate_trigger_section(self, document: Document) -> str:
"""Generate trigger section."""
return '''## Trigger
Event: [Specify triggering event] Timing: [before/after the event]
Trigger Conditions
This hook triggers when:
-
[Condition 1]
-
[Condition 2] '''
def _generate_trigger_conditions(self) -> str: """Generate trigger conditions.""" return '''### When This Hook Runs
| Condition | Triggers? |
|---|---|
| [Condition 1] | Yes/No |
| [Condition 2] | Yes/No |
| ''' |