Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:06:38 +08:00
commit ed3e4c84c3
76 changed files with 20449 additions and 0 deletions

53
hooks/utils/context-query.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
set -euo pipefail
CONTEXT_DIR="$(dirname "$0")/../context"
LOG_FILE="$CONTEXT_DIR/edit-log.txt"
# Get files edited since timestamp
get_recent_edits() {
local since="${1:-}"
if [ ! -f "$LOG_FILE" ]; then
return 0
fi
if [ -z "$since" ]; then
cat "$LOG_FILE" 2>/dev/null || true
else
awk -v since="$since" -F '|' '$1 >= since' "$LOG_FILE" 2>/dev/null || true
fi
}
# Get unique files edited in current session
get_session_files() {
local session_start="${1:-}"
get_recent_edits "$session_start" | \
awk -F '|' '{gsub(/^[ \t]+|[ \t]+$/, "", $4); print $4}' | \
sort -u
}
# Check if specific file was edited
was_file_edited() {
local file_path="$1"
local since="${2:-}"
get_recent_edits "$since" | grep -q "$(printf '%q' "$file_path")" 2>/dev/null
}
# Get edit count by repo
get_repo_stats() {
local since="${1:-}"
get_recent_edits "$since" | \
awk -F '|' '{gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2}' | \
sort | uniq -c | sort -rn
}
# Clear log (for testing)
clear_log() {
if [ -f "$LOG_FILE" ]; then
> "$LOG_FILE"
fi
}

105
hooks/utils/format-output.sh Executable file
View File

@@ -0,0 +1,105 @@
#!/usr/bin/env bash
set -e
check_dependencies() {
local missing=()
command -v jq >/dev/null 2>&1 || missing+=("jq")
if [ ${#missing[@]} -gt 0 ]; then
echo "ERROR: Missing required dependencies: ${missing[*]}" >&2
return 1
fi
return 0
}
check_dependencies || exit 1
# Get priority emoji for visual distinction
get_priority_emoji() {
local priority="$1"
case "$priority" in
"critical") echo "🔴" ;;
"high") echo "⭐" ;;
"medium") echo "📌" ;;
"low") echo "💡" ;;
*) echo "•" ;;
esac
}
# Format skill activation reminder
# Usage: format_skill_reminder <rules_path> <skill_name1> [<skill_name2> ...]
format_skill_reminder() {
local rules_path="$1"
shift
local skills=("$@")
if [ ${#skills[@]} -eq 0 ]; then
return 0
fi
echo "⚠️ SKILL ACTIVATION REMINDER"
echo ""
echo "The following skills may apply to your current task:"
echo ""
for skill in "${skills[@]}"; do
local priority=$(jq -r --arg skill "$skill" '.[$skill].priority // "medium"' "$rules_path")
local emoji=$(get_priority_emoji "$priority")
local skill_type=$(jq -r --arg skill "$skill" '.[$skill].type // "workflow"' "$rules_path")
echo "$emoji $skill ($skill_type, $priority priority)"
done
echo ""
echo "📖 Use the Skill tool to activate: Skill command=\"hyperpowers:$skill\""
echo ""
}
# Format gentle reminders for common workflow steps
format_gentle_reminder() {
local reminder_type="$1"
case "$reminder_type" in
"tdd")
cat <<'EOF'
💭 Remember: Test-Driven Development (TDD)
Before writing implementation code:
1. RED: Write the test first, watch it fail
2. GREEN: Write minimal code to pass
3. REFACTOR: Clean up while keeping tests green
Why? The failure proves your test actually tests something!
EOF
;;
"verification")
cat <<'EOF'
✅ Before claiming work is complete:
1. Run verification commands (tests, lints, builds)
2. Capture output as evidence
3. Only claim success if verification passes
Evidence before assertions, always.
EOF
;;
"testing-anti-patterns")
cat <<'EOF'
⚠️ Common Testing Anti-Patterns:
• Testing mock behavior instead of real behavior
• Adding test-only methods to production code
• Mocking without understanding dependencies
Test the real thing, not the test double!
EOF
;;
*)
echo "Unknown reminder type: $reminder_type"
return 1
;;
esac
}

142
hooks/utils/skill-matcher.sh Executable file
View File

@@ -0,0 +1,142 @@
#!/usr/bin/env bash
set -e
check_dependencies() {
local missing=()
command -v jq >/dev/null 2>&1 || missing+=("jq")
command -v grep >/dev/null 2>&1 || missing+=("grep")
if [ ${#missing[@]} -gt 0 ]; then
echo "ERROR: Missing required dependencies: ${missing[*]}" >&2
echo "Please install missing tools and try again." >&2
return 1
fi
return 0
}
check_dependencies || exit 1
# Load and validate skill-rules.json
load_skill_rules() {
local rules_path="$1"
if [ -z "$rules_path" ]; then
echo "ERROR: No rules path provided" >&2
return 1
fi
if [ ! -f "$rules_path" ]; then
echo "ERROR: Rules file not found: $rules_path" >&2
return 1
fi
if ! jq . "$rules_path" 2>/dev/null; then
echo "ERROR: Invalid JSON in $rules_path" >&2
return 1
fi
return 0
}
# Match keywords (case-insensitive substring matching)
match_keywords() {
local text="$1"
local keywords="$2"
if [ -z "$text" ] || [ -z "$keywords" ]; then
return 1
fi
local lower_text=$(echo "$text" | tr '[:upper:]' '[:lower:]')
IFS=',' read -ra KEYWORD_ARRAY <<< "$keywords"
for keyword in "${KEYWORD_ARRAY[@]}"; do
local lower_keyword=$(echo "$keyword" | tr '[:upper:]' '[:lower:]' | xargs)
if [[ "$lower_text" == *"$lower_keyword"* ]]; then
return 0
fi
done
return 1
}
# Match regex patterns (case-insensitive)
match_patterns() {
local text="$1"
local patterns="$2"
if [ -z "$text" ] || [ -z "$patterns" ]; then
return 1
fi
# Use bash regex matching for performance (no external process spawning)
local lower_text=$(echo "$text" | tr '[:upper:]' '[:lower:]')
IFS=',' read -ra PATTERN_ARRAY <<< "$patterns"
for pattern in "${PATTERN_ARRAY[@]}"; do
pattern=$(echo "$pattern" | xargs | tr '[:upper:]' '[:lower:]')
# Use bash's built-in regex matching (much faster than spawning grep)
if [[ "$lower_text" =~ $pattern ]]; then
return 0
fi
done
return 1
}
# Find matching skills from prompt
# Returns JSON array of skill names, sorted by priority
find_matching_skills() {
local prompt="$1"
local rules_path="$2"
local max_skills="${3:-3}"
if [ -z "$prompt" ] || [ -z "$rules_path" ]; then
echo "[]"
return 0
fi
if ! load_skill_rules "$rules_path" >/dev/null; then
echo "[]"
return 1
fi
# Load all skill data in one jq call for performance
local skill_data=$(jq -r '
to_entries |
map(select(.key != "_comment" and .key != "_schema")) |
map({
name: .key,
priority: .value.priority,
keywords: (.value.promptTriggers.keywords | join(",")),
patterns: (.value.promptTriggers.intentPatterns | join(","))
}) |
.[] |
"\(.name)|\(.priority)|\(.keywords)|\(.patterns)"
' "$rules_path")
local matches=()
while IFS='|' read -r skill priority keywords patterns; do
# Check if keywords or patterns match
if match_keywords "$prompt" "$keywords" || match_patterns "$prompt" "$patterns"; then
matches+=("$priority:$skill")
fi
done <<< "$skill_data"
# Sort by priority (critical > high > medium > low) and limit to max_skills
if [ ${#matches[@]} -eq 0 ]; then
echo "[]"
return 0
fi
# Sort and format as JSON array
printf '%s\n' "${matches[@]}" | \
sed 's/^critical:/0:/; s/^high:/1:/; s/^medium:/2:/; s/^low:/3:/' | \
sort -t: -k1,1n | \
head -n "$max_skills" | \
cut -d: -f2- | \
jq -R . | \
jq -s .
}

60
hooks/utils/test-performance.sh Executable file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
source utils/skill-matcher.sh
echo "=== Performance Tests ==="
echo ""
# Test 1: match_keywords performance (<50ms)
echo "Test 1: match_keywords performance"
prompt="I want to write a test for the login function"
keywords="test,testing,TDD,spec,unit test"
start=$(date +%s%N)
for i in {1..10}; do
match_keywords "$prompt" "$keywords" >/dev/null
done
end=$(date +%s%N)
duration_ns=$((end - start))
duration_ms=$((duration_ns / 1000000 / 10))
echo " Duration: ${duration_ms}ms (target: <50ms)"
if [ $duration_ms -lt 50 ]; then
echo " ✓ PASS"
else
echo " ✗ FAIL"
exit 1
fi
echo ""
# Test 2: find_matching_skills performance (<1000ms acceptable for 113 patterns)
echo "Test 2: find_matching_skills performance"
prompt="I want to implement a new feature with TDD"
rules_path="skill-rules.json"
start=$(date +%s%N)
result=$(find_matching_skills "$prompt" "$rules_path" 3)
end=$(date +%s%N)
duration_ns=$((end - start))
duration_ms=$((duration_ns / 1000000))
echo " Duration: ${duration_ms}ms (target: <1000ms for 19 skills, 113 patterns)"
echo " Matches found: $(echo "$result" | jq 'length')"
if [ $duration_ms -lt 1000 ]; then
echo " ✓ PASS"
else
echo " ✗ FAIL - Performance degradation detected"
exit 1
fi
# Note: 113 regex patterns × 19 skills with bash regex matching
# Typical user prompts are 10-50 words, matching completes in <600ms
# This is acceptable for a user-prompt-submit hook (runs once per prompt)
echo ""
echo "=== All Performance Tests Passed ==="