@fr0mpy/prompt-system 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +338 -0
- package/index.js +12 -0
- package/package.json +45 -0
- package/templates/CLAUDE.md +20 -0
- package/templates/agents/10x-simplifier.md +62 -0
- package/templates/agents/accessibility-auditor.md +60 -0
- package/templates/agents/api-contract-guardian.md +65 -0
- package/templates/agents/assumption-challenger.md +52 -0
- package/templates/agents/breaking-change-predictor.md +62 -0
- package/templates/agents/context-curator.md +53 -0
- package/templates/agents/context-loader.md +112 -0
- package/templates/agents/decision-logger.md +68 -0
- package/templates/agents/dependency-detective.md +58 -0
- package/templates/agents/devils-advocate.md +65 -0
- package/templates/agents/error-boundary-designer.md +65 -0
- package/templates/agents/future-you.md +63 -0
- package/templates/agents/incident-replayer.md +62 -0
- package/templates/agents/intent-clarifier.md +45 -0
- package/templates/agents/legacy-archaeologist.md +54 -0
- package/templates/agents/migration-planner.md +61 -0
- package/templates/agents/package-checker.md +33 -0
- package/templates/agents/performance-profiler.md +61 -0
- package/templates/agents/pr-narrator.md +49 -0
- package/templates/agents/pre-code-check.md +48 -0
- package/templates/agents/refactor-scope-limiter.md +54 -0
- package/templates/agents/rubber-duck.md +55 -0
- package/templates/agents/scope-creep-detector.md +61 -0
- package/templates/agents/session-handoff.md +57 -0
- package/templates/agents/structure-validator.md +43 -0
- package/templates/agents/test-gap-finder.md +71 -0
- package/templates/commands/commit.md +9 -0
- package/templates/commands/review.md +12 -0
- package/templates/commands/test.md +11 -0
- package/templates/hooks/session-start.sh +108 -0
- package/templates/hooks/smart-inject-llm.sh +267 -0
- package/templates/hooks/smart-inject-rules.sh +300 -0
- package/templates/hooks/triggers.d/.gitkeep +0 -0
- package/templates/hooks/triggers.json +174 -0
- package/templates/rules/agent-lifecycle-messages.md +77 -0
- package/templates/rules/announce-actions.md +58 -0
- package/templates/rules/code-standards.md +74 -0
- package/templates/rules/command-format.md +60 -0
- package/templates/rules/constructive-pushback.md +62 -0
- package/templates/rules/context-passing-protocol.md +75 -0
- package/templates/rules/rule-format.md +72 -0
- package/templates/rules/subagent-format.md +94 -0
- package/templates/settings.json +47 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Session Start Hook
|
|
3
|
+
# - Checks if context is stale
|
|
4
|
+
# - Outputs instruction for Claude to use context-loader agent
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
CLAUDE_DIR=""
|
|
9
|
+
CURRENT_DIR="$(pwd)"
|
|
10
|
+
while [[ "$CURRENT_DIR" != "/" ]]; do
|
|
11
|
+
if [[ -d "$CURRENT_DIR/.claude" ]]; then
|
|
12
|
+
CLAUDE_DIR="$CURRENT_DIR/.claude"
|
|
13
|
+
break
|
|
14
|
+
fi
|
|
15
|
+
CURRENT_DIR="$(dirname "$CURRENT_DIR")"
|
|
16
|
+
done
|
|
17
|
+
|
|
18
|
+
[[ -z "$CLAUDE_DIR" ]] && exit 0
|
|
19
|
+
|
|
20
|
+
CONTEXT_FILE="$CLAUDE_DIR/CONTEXT.md"
|
|
21
|
+
FRESHNESS_THRESHOLD=3600 # 1 hour in seconds
|
|
22
|
+
|
|
23
|
+
# ============================================================================
|
|
24
|
+
# FUNCTIONS
|
|
25
|
+
# ============================================================================
|
|
26
|
+
|
|
27
|
+
is_fresh() {
|
|
28
|
+
[[ ! -f "$CONTEXT_FILE" ]] && return 1
|
|
29
|
+
|
|
30
|
+
local file_age
|
|
31
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
32
|
+
file_age=$(( $(date +%s) - $(stat -f %m "$CONTEXT_FILE") ))
|
|
33
|
+
else
|
|
34
|
+
file_age=$(( $(date +%s) - $(stat -c %Y "$CONTEXT_FILE") ))
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
[[ $file_age -lt $FRESHNESS_THRESHOLD ]]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get_context_summary() {
|
|
41
|
+
if [[ -f "$CONTEXT_FILE" ]]; then
|
|
42
|
+
# Extract quick stats section
|
|
43
|
+
grep -A5 "## Quick Stats" "$CONTEXT_FILE" 2>/dev/null | tail -4 | sed 's/^/ /'
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# ============================================================================
|
|
48
|
+
# MAIN
|
|
49
|
+
# ============================================================================
|
|
50
|
+
|
|
51
|
+
echo "<session_context>"
|
|
52
|
+
echo "┌─────────────────────────────────────────────┐"
|
|
53
|
+
echo "│ 🚀 SESSION STARTED │"
|
|
54
|
+
|
|
55
|
+
if [[ ! -f "$CONTEXT_FILE" ]]; then
|
|
56
|
+
echo "│ ├─ 📊 Context: ❌ NOT FOUND │"
|
|
57
|
+
echo "│ ├─ 📁 Expected: .claude/CONTEXT.md │"
|
|
58
|
+
echo "│ └─ ⚠️ Action: Run context-loader agent │"
|
|
59
|
+
echo "└─────────────────────────────────────────────┘"
|
|
60
|
+
echo ""
|
|
61
|
+
echo "📋 NEXT STEPS:"
|
|
62
|
+
echo " 1. 🚀 DELEGATE TO: context-loader"
|
|
63
|
+
echo " 2. Agent will scan codebase"
|
|
64
|
+
echo " 3. Generate .claude/CONTEXT.md"
|
|
65
|
+
echo ""
|
|
66
|
+
echo "⚠️ ENFORCEMENT: Run context-loader BEFORE any code tasks"
|
|
67
|
+
|
|
68
|
+
elif ! is_fresh; then
|
|
69
|
+
# Calculate human-readable age
|
|
70
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
71
|
+
file_age=$(( $(date +%s) - $(stat -f %m "$CONTEXT_FILE") ))
|
|
72
|
+
else
|
|
73
|
+
file_age=$(( $(date +%s) - $(stat -c %Y "$CONTEXT_FILE") ))
|
|
74
|
+
fi
|
|
75
|
+
hours=$((file_age / 3600))
|
|
76
|
+
mins=$(( (file_age % 3600) / 60 ))
|
|
77
|
+
|
|
78
|
+
echo "│ ├─ 📊 Context: ⚠️ STALE (${hours}h ${mins}m old) │"
|
|
79
|
+
echo "│ ├─ 📁 File: .claude/CONTEXT.md │"
|
|
80
|
+
echo "│ └─ ⚠️ Action: Refresh recommended │"
|
|
81
|
+
echo "└─────────────────────────────────────────────┘"
|
|
82
|
+
echo ""
|
|
83
|
+
echo "📋 CURRENT CONTEXT (stale):"
|
|
84
|
+
get_context_summary
|
|
85
|
+
echo ""
|
|
86
|
+
echo "💡 RECOMMENDATION: Run context-loader to refresh"
|
|
87
|
+
|
|
88
|
+
else
|
|
89
|
+
# Calculate age
|
|
90
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
91
|
+
file_age=$(( $(date +%s) - $(stat -f %m "$CONTEXT_FILE") ))
|
|
92
|
+
else
|
|
93
|
+
file_age=$(( $(date +%s) - $(stat -c %Y "$CONTEXT_FILE") ))
|
|
94
|
+
fi
|
|
95
|
+
mins=$((file_age / 60))
|
|
96
|
+
|
|
97
|
+
echo "│ ├─ 📊 Context: ✅ FRESH (${mins}m ago) │"
|
|
98
|
+
echo "│ ├─ 📁 File: .claude/CONTEXT.md │"
|
|
99
|
+
echo "│ └─ ✅ Ready to proceed │"
|
|
100
|
+
echo "└─────────────────────────────────────────────┘"
|
|
101
|
+
echo ""
|
|
102
|
+
echo "📋 PROJECT SUMMARY:"
|
|
103
|
+
get_context_summary
|
|
104
|
+
echo ""
|
|
105
|
+
echo "📁 Full details: .claude/CONTEXT.md"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
echo "</session_context>"
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# LLM-powered smart rule/agent injection using Claude Haiku
|
|
5
|
+
# Replaces static keyword matching with semantic understanding
|
|
6
|
+
|
|
7
|
+
# Read prompt from stdin
|
|
8
|
+
PROMPT=$(cat)
|
|
9
|
+
|
|
10
|
+
# Find .claude directory
|
|
11
|
+
find_claude_dir() {
|
|
12
|
+
local dir="$PWD"
|
|
13
|
+
while [[ "$dir" != "/" ]]; do
|
|
14
|
+
if [[ -d "$dir/.claude" ]]; then
|
|
15
|
+
echo "$dir/.claude"
|
|
16
|
+
return 0
|
|
17
|
+
fi
|
|
18
|
+
dir="$(dirname "$dir")"
|
|
19
|
+
done
|
|
20
|
+
return 1
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
CLAUDE_DIR=$(find_claude_dir) || { echo "<user-prompt-submit-hook>⚠️ .claude directory not found</user-prompt-submit-hook>"; exit 0; }
|
|
24
|
+
|
|
25
|
+
RULES_DIR="$CLAUDE_DIR/rules"
|
|
26
|
+
AGENTS_DIR="$CLAUDE_DIR/agents"
|
|
27
|
+
|
|
28
|
+
# Check directories exist
|
|
29
|
+
[[ -d "$RULES_DIR" ]] || { echo "<user-prompt-submit-hook>⚠️ Rules directory not found at $RULES_DIR</user-prompt-submit-hook>"; exit 0; }
|
|
30
|
+
|
|
31
|
+
# Get API key and model from environment
|
|
32
|
+
API_KEY="${ANTHROPIC_API_KEY:-}"
|
|
33
|
+
[[ -n "$API_KEY" ]] || { echo "<user-prompt-submit-hook>ℹ️ ANTHROPIC_API_KEY not set - using keyword matching</user-prompt-submit-hook>"; exec "$CLAUDE_DIR/hooks/smart-inject-rules.sh" <<< "$PROMPT"; }
|
|
34
|
+
|
|
35
|
+
MODEL="${ANTHROPIC_MODEL:-}"
|
|
36
|
+
[[ -n "$MODEL" ]] || { echo "<user-prompt-submit-hook>❌ ANTHROPIC_MODEL not set. Add ANTHROPIC_MODEL to your .env file (e.g., claude-3-5-haiku-latest)</user-prompt-submit-hook>"; exit 1; }
|
|
37
|
+
|
|
38
|
+
# Build rules summary (name + description from YAML frontmatter)
|
|
39
|
+
RULES_SUMMARY=""
|
|
40
|
+
for rule_file in "$RULES_DIR"/*.md; do
|
|
41
|
+
[[ -f "$rule_file" ]] || continue
|
|
42
|
+
RULE_NAME=$(basename "$rule_file" .md)
|
|
43
|
+
# Try YAML frontmatter first, fall back to SUMMARY comment
|
|
44
|
+
RULE_DESC=$(sed -n '/^---$/,/^---$/p' "$rule_file" 2>/dev/null | grep -m1 "^description:" | sed 's/description: *//' | tr -d '\n')
|
|
45
|
+
if [[ -z "$RULE_DESC" ]]; then
|
|
46
|
+
RULE_DESC=$(grep -m1 "<!-- SUMMARY:" "$rule_file" 2>/dev/null | sed 's/.*SUMMARY: *\([^-]*\) *-->.*/\1/' | tr -d '\n')
|
|
47
|
+
fi
|
|
48
|
+
[[ -n "$RULE_DESC" ]] && RULES_SUMMARY+="- $RULE_NAME: $RULE_DESC\n"
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
# Build agents summary
|
|
52
|
+
AGENTS_SUMMARY=""
|
|
53
|
+
if [[ -d "$AGENTS_DIR" ]]; then
|
|
54
|
+
for agent_file in "$AGENTS_DIR"/*.md; do
|
|
55
|
+
[[ -f "$agent_file" ]] || continue
|
|
56
|
+
AGENT_NAME=$(basename "$agent_file" .md)
|
|
57
|
+
AGENT_DESC=$(grep -m1 "^description:" "$agent_file" 2>/dev/null | sed 's/description: *//' | tr -d '\n')
|
|
58
|
+
AGENTS_SUMMARY+="- $AGENT_NAME: $AGENT_DESC\n"
|
|
59
|
+
done
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Escape prompt for JSON
|
|
63
|
+
ESCAPED_PROMPT=$(echo "$PROMPT" | jq -Rs '.')
|
|
64
|
+
ESCAPED_RULES=$(echo -e "$RULES_SUMMARY" | jq -Rs '.')
|
|
65
|
+
ESCAPED_AGENTS=$(echo -e "$AGENTS_SUMMARY" | jq -Rs '.')
|
|
66
|
+
|
|
67
|
+
# Build dynamic system prompt from actual files
|
|
68
|
+
SYSTEM_PROMPT="You analyze user prompts and select relevant rules/agents. Respond ONLY with valid JSON.
|
|
69
|
+
|
|
70
|
+
Output format:
|
|
71
|
+
{
|
|
72
|
+
\"active_rules\": [\"rule-name\"],
|
|
73
|
+
\"suggested_agents\": [\"agent-name\"],
|
|
74
|
+
\"enforcement\": \"Brief instruction Claude MUST follow\"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Available rules (select based on their descriptions):
|
|
78
|
+
$(echo -e "$RULES_SUMMARY")
|
|
79
|
+
|
|
80
|
+
Available agents (select based on their descriptions):
|
|
81
|
+
$(echo -e "$AGENTS_SUMMARY")
|
|
82
|
+
|
|
83
|
+
Selection guidelines:
|
|
84
|
+
- Read each rule/agent description to understand when it applies
|
|
85
|
+
- Select rules that match the user's intent, not just keywords
|
|
86
|
+
- Select agents that should run BEFORE or AFTER the task
|
|
87
|
+
- If no rules/agents apply, return empty arrays
|
|
88
|
+
|
|
89
|
+
Enforcement rules:
|
|
90
|
+
- If you select ANY agent, enforcement MUST say \"YOU MUST run [agent-name] FIRST\" or \"YOU MUST run [agent-name] AFTER\"
|
|
91
|
+
- If you select rules, include their key requirement in enforcement
|
|
92
|
+
- Enforcement should be SHORT (<100 chars) but MANDATORY language
|
|
93
|
+
|
|
94
|
+
Good enforcement examples:
|
|
95
|
+
- \"YOU MUST run pre-code-check BEFORE writing code\"
|
|
96
|
+
- \"YOU MUST run package-checker BEFORE installing\"
|
|
97
|
+
- \"Prefix actions with 🔧. YOU MUST run pre-code-check FIRST\"
|
|
98
|
+
|
|
99
|
+
Bad enforcement examples:
|
|
100
|
+
- \"Consider running pre-code-check\" (too weak)
|
|
101
|
+
- \"pre-code-check suggested\" (not mandatory)
|
|
102
|
+
"
|
|
103
|
+
|
|
104
|
+
USER_MSG="User prompt: $PROMPT
|
|
105
|
+
|
|
106
|
+
Based on the prompt, select relevant rules and agents. Return JSON only."
|
|
107
|
+
|
|
108
|
+
ESCAPED_SYSTEM=$(printf '%s' "$SYSTEM_PROMPT" | jq -Rs '.')
|
|
109
|
+
ESCAPED_USER=$(printf '%s' "$USER_MSG" | jq -Rs '.')
|
|
110
|
+
|
|
111
|
+
# Call Haiku API
|
|
112
|
+
RESPONSE=$(curl -s --max-time 5 "https://api.anthropic.com/v1/messages" \
|
|
113
|
+
-H "Content-Type: application/json" \
|
|
114
|
+
-H "x-api-key: $API_KEY" \
|
|
115
|
+
-H "anthropic-version: 2023-06-01" \
|
|
116
|
+
-d "{
|
|
117
|
+
\"model\": \"$MODEL\",
|
|
118
|
+
\"max_tokens\": 256,
|
|
119
|
+
\"messages\": [{
|
|
120
|
+
\"role\": \"user\",
|
|
121
|
+
\"content\": $ESCAPED_USER
|
|
122
|
+
}],
|
|
123
|
+
\"system\": $ESCAPED_SYSTEM
|
|
124
|
+
}" 2>/dev/null) || { echo "<user-prompt-submit-hook>⚠️ LLM API call failed - using keyword matching</user-prompt-submit-hook>" >&2; exec "$CLAUDE_DIR/hooks/smart-inject-rules.sh" <<< "$PROMPT"; }
|
|
125
|
+
|
|
126
|
+
# Check for API errors
|
|
127
|
+
API_ERROR=$(echo "$RESPONSE" | jq -r '.error.message // empty' 2>/dev/null)
|
|
128
|
+
if [[ -n "$API_ERROR" ]]; then
|
|
129
|
+
echo "<user-prompt-submit-hook>⚠️ API error: $API_ERROR - using keyword matching</user-prompt-submit-hook>" >&2
|
|
130
|
+
exec "$CLAUDE_DIR/hooks/smart-inject-rules.sh" <<< "$PROMPT"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# Extract JSON from response
|
|
134
|
+
LLM_OUTPUT=$(echo "$RESPONSE" | jq -r '.content[0].text // empty' 2>/dev/null) || { echo "<user-prompt-submit-hook>⚠️ Failed to parse LLM response - using keyword matching</user-prompt-submit-hook>" >&2; exec "$CLAUDE_DIR/hooks/smart-inject-rules.sh" <<< "$PROMPT"; }
|
|
135
|
+
|
|
136
|
+
[[ -n "$LLM_OUTPUT" ]] || { echo "<user-prompt-submit-hook>⚠️ Empty LLM response - using keyword matching</user-prompt-submit-hook>" >&2; exec "$CLAUDE_DIR/hooks/smart-inject-rules.sh" <<< "$PROMPT"; }
|
|
137
|
+
|
|
138
|
+
# Parse LLM response
|
|
139
|
+
ACTIVE_RULES=$(echo "$LLM_OUTPUT" | jq -r '.active_rules[]? // empty' 2>/dev/null)
|
|
140
|
+
SUGGESTED_AGENTS=$(echo "$LLM_OUTPUT" | jq -r '.suggested_agents[]? // empty' 2>/dev/null)
|
|
141
|
+
ENFORCEMENT=$(echo "$LLM_OUTPUT" | jq -r '.enforcement // empty' 2>/dev/null)
|
|
142
|
+
|
|
143
|
+
# Check context freshness
|
|
144
|
+
CONTEXT_FILE="$CLAUDE_DIR/CONTEXT.md"
|
|
145
|
+
CONTEXT_STATUS="fresh"
|
|
146
|
+
CONTEXT_AGE=""
|
|
147
|
+
|
|
148
|
+
if [[ ! -f "$CONTEXT_FILE" ]]; then
|
|
149
|
+
CONTEXT_STATUS="missing"
|
|
150
|
+
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
151
|
+
file_age=$(( $(date +%s) - $(stat -f %m "$CONTEXT_FILE") ))
|
|
152
|
+
CONTEXT_AGE="${file_age}s"
|
|
153
|
+
[[ $file_age -gt 3600 ]] && CONTEXT_STATUS="stale"
|
|
154
|
+
else
|
|
155
|
+
file_age=$(( $(date +%s) - $(stat -c %Y "$CONTEXT_FILE") ))
|
|
156
|
+
CONTEXT_AGE="${file_age}s"
|
|
157
|
+
[[ $file_age -gt 3600 ]] && CONTEXT_STATUS="stale"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# Build rich output
|
|
161
|
+
OUTPUT="<user-prompt-submit-hook>\n"
|
|
162
|
+
OUTPUT+="┌─────────────────────────────────────────────┐\n"
|
|
163
|
+
OUTPUT+="│ 🎯 PROMPT ANALYZED (LLM) │\n"
|
|
164
|
+
|
|
165
|
+
# Context status
|
|
166
|
+
case "$CONTEXT_STATUS" in
|
|
167
|
+
fresh)
|
|
168
|
+
OUTPUT+="│ ├─ 📊 Context: ✅ fresh ($CONTEXT_AGE ago) │\n"
|
|
169
|
+
;;
|
|
170
|
+
stale)
|
|
171
|
+
OUTPUT+="│ ├─ 📊 Context: ⚠️ stale ($CONTEXT_AGE ago) │\n"
|
|
172
|
+
;;
|
|
173
|
+
missing)
|
|
174
|
+
OUTPUT+="│ ├─ 📊 Context: ❌ missing │\n"
|
|
175
|
+
;;
|
|
176
|
+
esac
|
|
177
|
+
|
|
178
|
+
# Add active rules
|
|
179
|
+
if [[ -n "$ACTIVE_RULES" ]]; then
|
|
180
|
+
rules_list=$(echo "$ACTIVE_RULES" | tr '\n' ', ' | sed 's/,$//')
|
|
181
|
+
OUTPUT+="│ ├─ 📋 Rules: $rules_list\n"
|
|
182
|
+
else
|
|
183
|
+
OUTPUT+="│ ├─ 📋 Rules: (none matched) │\n"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# Add suggested agents
|
|
187
|
+
if [[ -n "$SUGGESTED_AGENTS" ]]; then
|
|
188
|
+
agents_list=$(echo "$SUGGESTED_AGENTS" | tr '\n' ', ' | sed 's/,$//')
|
|
189
|
+
OUTPUT+="│ └─ 🤖 Agents: $agents_list\n"
|
|
190
|
+
else
|
|
191
|
+
OUTPUT+="│ └─ 🤖 Agents: (none suggested) │\n"
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
OUTPUT+="└─────────────────────────────────────────────┘\n"
|
|
195
|
+
|
|
196
|
+
# Detailed agent info
|
|
197
|
+
if [[ -n "$SUGGESTED_AGENTS" ]]; then
|
|
198
|
+
OUTPUT+="\n📋 AGENT DETAILS:\n"
|
|
199
|
+
for agent in $SUGGESTED_AGENTS; do
|
|
200
|
+
agent_file="$AGENTS_DIR/${agent}.md"
|
|
201
|
+
if [[ -f "$agent_file" ]]; then
|
|
202
|
+
agent_desc=$(grep -m1 "^description:" "$agent_file" 2>/dev/null | sed 's/description: *//' | head -c 80)
|
|
203
|
+
OUTPUT+=" 🤖 $agent\n"
|
|
204
|
+
OUTPUT+=" └─ $agent_desc\n"
|
|
205
|
+
fi
|
|
206
|
+
done
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
# Add enforcement instruction if present
|
|
210
|
+
if [[ -n "$ENFORCEMENT" ]]; then
|
|
211
|
+
OUTPUT+="\n⚠️ ENFORCEMENT: $ENFORCEMENT\n"
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# Context warning if stale/missing
|
|
215
|
+
if [[ "$CONTEXT_STATUS" != "fresh" ]]; then
|
|
216
|
+
OUTPUT+="\n⚠️ CONTEXT WARNING: Run context-loader to refresh project context\n"
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
OUTPUT+="</user-prompt-submit-hook>"
|
|
220
|
+
|
|
221
|
+
echo -e "$OUTPUT"
|
|
222
|
+
|
|
223
|
+
# Helper function to get trigger from rule file
|
|
224
|
+
get_trigger() {
|
|
225
|
+
head -2 "$1" 2>/dev/null | grep '<!-- TRIGGER:' | sed 's/.*<!-- TRIGGER: //;s/ -->$//' || echo "always"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Always inject "always" triggered rules (announce-actions, etc.)
|
|
229
|
+
for f in "$RULES_DIR"/*.md; do
|
|
230
|
+
[[ -f "$f" ]] || continue
|
|
231
|
+
if [[ "$(get_trigger "$f")" == "always" ]]; then
|
|
232
|
+
rule_basename=$(basename "$f" .md)
|
|
233
|
+
echo ""
|
|
234
|
+
echo "<${rule_basename}_rule>"
|
|
235
|
+
grep -v '^<!-- ' "$f" | head -40
|
|
236
|
+
echo "</${rule_basename}_rule>"
|
|
237
|
+
fi
|
|
238
|
+
done
|
|
239
|
+
|
|
240
|
+
# Inject content of LLM-selected rules
|
|
241
|
+
if [[ -n "$ACTIVE_RULES" ]]; then
|
|
242
|
+
for rule in $ACTIVE_RULES; do
|
|
243
|
+
rule_file="$RULES_DIR/${rule}.md"
|
|
244
|
+
if [[ -f "$rule_file" ]]; then
|
|
245
|
+
echo ""
|
|
246
|
+
echo "<${rule}_rule>"
|
|
247
|
+
grep -v '^<!-- ' "$rule_file" | head -40
|
|
248
|
+
echo "</${rule}_rule>"
|
|
249
|
+
fi
|
|
250
|
+
done
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Inject lifecycle and context rules for agent operations
|
|
254
|
+
if [[ -n "$SUGGESTED_AGENTS" ]]; then
|
|
255
|
+
if [[ -f "$RULES_DIR/agent-lifecycle-messages.md" ]]; then
|
|
256
|
+
echo ""
|
|
257
|
+
echo "<lifecycle_rule>"
|
|
258
|
+
grep -v '^<!-- ' "$RULES_DIR/agent-lifecycle-messages.md" | head -50
|
|
259
|
+
echo "</lifecycle_rule>"
|
|
260
|
+
fi
|
|
261
|
+
if [[ -f "$RULES_DIR/context-passing-protocol.md" ]]; then
|
|
262
|
+
echo ""
|
|
263
|
+
echo "<context_protocol_rule>"
|
|
264
|
+
grep -v '^<!-- ' "$RULES_DIR/context-passing-protocol.md" | head -40
|
|
265
|
+
echo "</context_protocol_rule>"
|
|
266
|
+
fi
|
|
267
|
+
fi
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Smart Context Injection
|
|
3
|
+
# - Injects behavioral rules
|
|
4
|
+
# - Suggests agents for tasks that would bloat context
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
CLAUDE_DIR=""
|
|
9
|
+
CURRENT_DIR="$(pwd)"
|
|
10
|
+
while [[ "$CURRENT_DIR" != "/" ]]; do
|
|
11
|
+
if [[ -d "$CURRENT_DIR/.claude/rules" ]]; then
|
|
12
|
+
CLAUDE_DIR="$CURRENT_DIR/.claude"
|
|
13
|
+
break
|
|
14
|
+
fi
|
|
15
|
+
CURRENT_DIR="$(dirname "$CURRENT_DIR")"
|
|
16
|
+
done
|
|
17
|
+
|
|
18
|
+
[[ -z "$CLAUDE_DIR" ]] && { echo "<context_injection>⚠️ .claude directory not found</context_injection>"; exit 0; }
|
|
19
|
+
|
|
20
|
+
RULES_DIR="$CLAUDE_DIR/rules"
|
|
21
|
+
AGENTS_DIR="$CLAUDE_DIR/agents"
|
|
22
|
+
TRIGGERS_FILE="$CLAUDE_DIR/hooks/triggers.json"
|
|
23
|
+
TRIGGERS_D_DIR="$CLAUDE_DIR/hooks/triggers.d"
|
|
24
|
+
|
|
25
|
+
# ============================================================================
|
|
26
|
+
# FUNCTIONS
|
|
27
|
+
# ============================================================================
|
|
28
|
+
|
|
29
|
+
get_summary() {
|
|
30
|
+
head -1 "$1" 2>/dev/null | grep '<!-- SUMMARY:' | sed 's/.*<!-- SUMMARY: //;s/ -->$//' || echo ""
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get_trigger() {
|
|
34
|
+
head -2 "$1" 2>/dev/null | grep '<!-- TRIGGER:' | sed 's/.*<!-- TRIGGER: //;s/ -->$//' || echo "always"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get_rule_name() {
|
|
38
|
+
basename "$1" .md | tr '-' ' ' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get_agent_name() {
|
|
42
|
+
grep '^name:' "$1" 2>/dev/null | sed 's/name: *//' || basename "$1" .md
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get_agent_desc() {
|
|
46
|
+
grep '^description:' "$1" 2>/dev/null | sed 's/description: *//' | head -c 80 || echo ""
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
parse_json_array_from_file() {
|
|
50
|
+
local trigger="$1"
|
|
51
|
+
local file="$2"
|
|
52
|
+
local in_array=0
|
|
53
|
+
local keywords=""
|
|
54
|
+
|
|
55
|
+
while IFS= read -r line; do
|
|
56
|
+
if [[ $in_array -eq 0 ]] && echo "$line" | grep -q "\"$trigger\":"; then
|
|
57
|
+
in_array=1
|
|
58
|
+
continue
|
|
59
|
+
fi
|
|
60
|
+
if [[ $in_array -eq 1 ]]; then
|
|
61
|
+
if echo "$line" | grep -q '\]'; then
|
|
62
|
+
break
|
|
63
|
+
fi
|
|
64
|
+
local kw=$(echo "$line" | tr -d '",[] ' | tr -d '\t')
|
|
65
|
+
[[ -n "$kw" ]] && keywords="$keywords|$kw"
|
|
66
|
+
fi
|
|
67
|
+
done < "$file"
|
|
68
|
+
|
|
69
|
+
echo "$keywords" | sed 's/^|//'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
parse_json_array() {
|
|
73
|
+
local trigger="$1"
|
|
74
|
+
parse_json_array_from_file "$trigger" "$TRIGGERS_FILE"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Parse extension triggers from triggers.d/
|
|
78
|
+
parse_extension_triggers() {
|
|
79
|
+
local trigger="$1"
|
|
80
|
+
local keywords=""
|
|
81
|
+
|
|
82
|
+
if [[ -d "$TRIGGERS_D_DIR" ]]; then
|
|
83
|
+
for ext_file in "$TRIGGERS_D_DIR"/*.json; do
|
|
84
|
+
[[ ! -f "$ext_file" ]] && continue
|
|
85
|
+
local ext_keywords=$(parse_json_array_from_file "$trigger" "$ext_file")
|
|
86
|
+
[[ -n "$ext_keywords" ]] && keywords="$keywords|$ext_keywords"
|
|
87
|
+
done
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
echo "$keywords" | sed 's/^|//'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Get all trigger names from main file and extensions
|
|
94
|
+
get_all_trigger_names() {
|
|
95
|
+
local names=""
|
|
96
|
+
|
|
97
|
+
# From main triggers file
|
|
98
|
+
if [[ -f "$TRIGGERS_FILE" ]]; then
|
|
99
|
+
names=$(grep '": \[' "$TRIGGERS_FILE" | grep -v '_comment' | sed 's/.*"\([^"]*\)".*/\1/')
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# From extension files
|
|
103
|
+
if [[ -d "$TRIGGERS_D_DIR" ]]; then
|
|
104
|
+
for ext_file in "$TRIGGERS_D_DIR"/*.json; do
|
|
105
|
+
[[ ! -f "$ext_file" ]] && continue
|
|
106
|
+
local ext_names=$(grep '": \[' "$ext_file" | grep -v '_comment' | sed 's/.*"\([^"]*\)".*/\1/')
|
|
107
|
+
names="$names $ext_names"
|
|
108
|
+
done
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
echo "$names" | tr ' ' '\n' | sort -u | tr '\n' ' '
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# ============================================================================
|
|
115
|
+
# MAIN
|
|
116
|
+
# ============================================================================
|
|
117
|
+
|
|
118
|
+
PROMPT=""
|
|
119
|
+
[[ ! -t 0 ]] && PROMPT=$(cat)
|
|
120
|
+
PROMPT_LOWER=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]')
|
|
121
|
+
|
|
122
|
+
MATCHED_TRIGGERS=""
|
|
123
|
+
|
|
124
|
+
# Get all trigger names from main file and extensions
|
|
125
|
+
TRIGGER_NAMES=$(get_all_trigger_names)
|
|
126
|
+
|
|
127
|
+
for trigger in $TRIGGER_NAMES; do
|
|
128
|
+
# Get keywords from main file
|
|
129
|
+
KEYWORDS=$(parse_json_array "$trigger")
|
|
130
|
+
# Also get from extensions
|
|
131
|
+
EXT_KEYWORDS=$(parse_extension_triggers "$trigger")
|
|
132
|
+
[[ -n "$EXT_KEYWORDS" ]] && KEYWORDS="$KEYWORDS|$EXT_KEYWORDS"
|
|
133
|
+
KEYWORDS=$(echo "$KEYWORDS" | sed 's/^|//;s/|$//')
|
|
134
|
+
|
|
135
|
+
if [[ -n "$KEYWORDS" ]] && echo "$PROMPT_LOWER" | grep -qiE "$KEYWORDS"; then
|
|
136
|
+
MATCHED_TRIGGERS="$MATCHED_TRIGGERS $trigger"
|
|
137
|
+
fi
|
|
138
|
+
done
|
|
139
|
+
|
|
140
|
+
MATCHED_TRIGGERS=$(echo "$MATCHED_TRIGGERS" | sed 's/^ //')
|
|
141
|
+
|
|
142
|
+
# ============================================================================
|
|
143
|
+
# CHECK CONTEXT FRESHNESS
|
|
144
|
+
# ============================================================================
|
|
145
|
+
|
|
146
|
+
CONTEXT_FILE="$CLAUDE_DIR/CONTEXT.md"
|
|
147
|
+
CONTEXT_STATUS="fresh"
|
|
148
|
+
CONTEXT_AGE=""
|
|
149
|
+
|
|
150
|
+
if [[ ! -f "$CONTEXT_FILE" ]]; then
|
|
151
|
+
CONTEXT_STATUS="missing"
|
|
152
|
+
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
153
|
+
file_age=$(( $(date +%s) - $(stat -f %m "$CONTEXT_FILE") ))
|
|
154
|
+
CONTEXT_AGE="${file_age}s"
|
|
155
|
+
[[ $file_age -gt 3600 ]] && CONTEXT_STATUS="stale"
|
|
156
|
+
else
|
|
157
|
+
file_age=$(( $(date +%s) - $(stat -c %Y "$CONTEXT_FILE") ))
|
|
158
|
+
CONTEXT_AGE="${file_age}s"
|
|
159
|
+
[[ $file_age -gt 3600 ]] && CONTEXT_STATUS="stale"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# ============================================================================
|
|
163
|
+
# OUTPUT
|
|
164
|
+
# ============================================================================
|
|
165
|
+
|
|
166
|
+
echo "<context_injection>"
|
|
167
|
+
echo "┌─────────────────────────────────────────────┐"
|
|
168
|
+
echo "│ 🎯 PROMPT RECEIVED │"
|
|
169
|
+
|
|
170
|
+
# Context status line
|
|
171
|
+
case "$CONTEXT_STATUS" in
|
|
172
|
+
fresh)
|
|
173
|
+
echo "│ ├─ 📊 Context: ✅ fresh ($CONTEXT_AGE ago) │"
|
|
174
|
+
;;
|
|
175
|
+
stale)
|
|
176
|
+
echo "│ ├─ 📊 Context: ⚠️ stale ($CONTEXT_AGE ago) │"
|
|
177
|
+
;;
|
|
178
|
+
missing)
|
|
179
|
+
echo "│ ├─ 📊 Context: ❌ missing │"
|
|
180
|
+
;;
|
|
181
|
+
esac
|
|
182
|
+
|
|
183
|
+
# Rules status
|
|
184
|
+
ACTIVE_RULES=""
|
|
185
|
+
for f in "$RULES_DIR"/*.md; do
|
|
186
|
+
[[ ! -f "$f" ]] && continue
|
|
187
|
+
local_summary=$(get_summary "$f")
|
|
188
|
+
local_trigger=$(get_trigger "$f")
|
|
189
|
+
[[ -z "$local_summary" ]] && continue
|
|
190
|
+
|
|
191
|
+
if echo " $MATCHED_TRIGGERS " | grep -q " $local_trigger "; then
|
|
192
|
+
rule_name=$(get_rule_name "$f")
|
|
193
|
+
ACTIVE_RULES="$ACTIVE_RULES$rule_name, "
|
|
194
|
+
fi
|
|
195
|
+
done
|
|
196
|
+
ACTIVE_RULES=$(echo "$ACTIVE_RULES" | sed 's/, $//')
|
|
197
|
+
|
|
198
|
+
if [[ -n "$ACTIVE_RULES" ]]; then
|
|
199
|
+
echo "│ ├─ 📋 Rules: $ACTIVE_RULES"
|
|
200
|
+
else
|
|
201
|
+
echo "│ ├─ 📋 Rules: (none matched) │"
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# Agent suggestions based on triggers
|
|
205
|
+
# Core mappings are here; extensions can add their own via triggers.d/
|
|
206
|
+
SUGGESTED_AGENTS=""
|
|
207
|
+
for trigger in $MATCHED_TRIGGERS; do
|
|
208
|
+
case "$trigger" in
|
|
209
|
+
code) SUGGESTED_AGENTS="$SUGGESTED_AGENTS pre-code-check" ;;
|
|
210
|
+
package) SUGGESTED_AGENTS="$SUGGESTED_AGENTS package-checker dependency-detective" ;;
|
|
211
|
+
structure) SUGGESTED_AGENTS="$SUGGESTED_AGENTS structure-validator" ;;
|
|
212
|
+
planning) SUGGESTED_AGENTS="$SUGGESTED_AGENTS intent-clarifier assumption-challenger" ;;
|
|
213
|
+
debug) SUGGESTED_AGENTS="$SUGGESTED_AGENTS incident-replayer error-boundary-designer" ;;
|
|
214
|
+
review) SUGGESTED_AGENTS="$SUGGESTED_AGENTS future-you scope-creep-detector" ;;
|
|
215
|
+
refactor) SUGGESTED_AGENTS="$SUGGESTED_AGENTS refactor-scope-limiter 10x-simplifier" ;;
|
|
216
|
+
test) SUGGESTED_AGENTS="$SUGGESTED_AGENTS test-gap-finder" ;;
|
|
217
|
+
accessibility) SUGGESTED_AGENTS="$SUGGESTED_AGENTS accessibility-auditor" ;;
|
|
218
|
+
migration) SUGGESTED_AGENTS="$SUGGESTED_AGENTS migration-planner breaking-change-predictor" ;;
|
|
219
|
+
performance) SUGGESTED_AGENTS="$SUGGESTED_AGENTS performance-profiler" ;;
|
|
220
|
+
pr) SUGGESTED_AGENTS="$SUGGESTED_AGENTS pr-narrator" ;;
|
|
221
|
+
session) SUGGESTED_AGENTS="$SUGGESTED_AGENTS session-handoff context-curator" ;;
|
|
222
|
+
thinking) SUGGESTED_AGENTS="$SUGGESTED_AGENTS rubber-duck devils-advocate" ;;
|
|
223
|
+
# Extension triggers (from triggers.d/) are handled dynamically
|
|
224
|
+
# No default case - unknown triggers are fine, just no agent suggestion
|
|
225
|
+
esac
|
|
226
|
+
done
|
|
227
|
+
|
|
228
|
+
if [[ -n "$SUGGESTED_AGENTS" ]]; then
|
|
229
|
+
agent_list=$(echo "$SUGGESTED_AGENTS" | sed 's/^ //' | tr ' ' ', ')
|
|
230
|
+
echo "│ └─ 🤖 Agents: $agent_list"
|
|
231
|
+
else
|
|
232
|
+
echo "│ └─ 🤖 Agents: (none suggested) │"
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
echo "└─────────────────────────────────────────────┘"
|
|
236
|
+
|
|
237
|
+
# Detailed agent info with enforcement
|
|
238
|
+
if [[ -n "$SUGGESTED_AGENTS" ]]; then
|
|
239
|
+
echo ""
|
|
240
|
+
echo "📋 AGENT DETAILS:"
|
|
241
|
+
for agent in $SUGGESTED_AGENTS; do
|
|
242
|
+
agent_file="$AGENTS_DIR/${agent}.md"
|
|
243
|
+
if [[ -f "$agent_file" ]]; then
|
|
244
|
+
echo " 🤖 $agent"
|
|
245
|
+
echo " └─ $(get_agent_desc "$agent_file")"
|
|
246
|
+
fi
|
|
247
|
+
done
|
|
248
|
+
echo ""
|
|
249
|
+
first_agent=$(echo "$SUGGESTED_AGENTS" | awk '{print $1}')
|
|
250
|
+
echo "⚠️ ENFORCEMENT: Run $first_agent BEFORE proceeding"
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Context warning if stale/missing
|
|
254
|
+
if [[ "$CONTEXT_STATUS" != "fresh" ]]; then
|
|
255
|
+
echo ""
|
|
256
|
+
echo "⚠️ CONTEXT WARNING: Run context-loader to refresh project context"
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
echo "</context_injection>"
|
|
260
|
+
|
|
261
|
+
# Inject full rules only for matched behavioral triggers
|
|
262
|
+
for trigger in $MATCHED_TRIGGERS; do
|
|
263
|
+
for f in "$RULES_DIR"/*.md; do
|
|
264
|
+
[[ ! -f "$f" ]] && continue
|
|
265
|
+
if [[ "$(get_trigger "$f")" == "$trigger" ]]; then
|
|
266
|
+
echo ""
|
|
267
|
+
echo "<${trigger}_rule>"
|
|
268
|
+
grep -v '^<!-- ' "$f" | head -40
|
|
269
|
+
echo "</${trigger}_rule>"
|
|
270
|
+
fi
|
|
271
|
+
done
|
|
272
|
+
done
|
|
273
|
+
|
|
274
|
+
# Always inject "always" triggered rules (announce-actions, etc.)
|
|
275
|
+
for f in "$RULES_DIR"/*.md; do
|
|
276
|
+
[[ ! -f "$f" ]] && continue
|
|
277
|
+
if [[ "$(get_trigger "$f")" == "always" ]]; then
|
|
278
|
+
rule_basename=$(basename "$f" .md)
|
|
279
|
+
echo ""
|
|
280
|
+
echo "<${rule_basename}_rule>"
|
|
281
|
+
grep -v '^<!-- ' "$f" | head -40
|
|
282
|
+
echo "</${rule_basename}_rule>"
|
|
283
|
+
fi
|
|
284
|
+
done
|
|
285
|
+
|
|
286
|
+
# Always inject lifecycle and context rules for agent operations
|
|
287
|
+
if [[ -n "$SUGGESTED_AGENTS" ]]; then
|
|
288
|
+
if [[ -f "$RULES_DIR/agent-lifecycle-messages.md" ]]; then
|
|
289
|
+
echo ""
|
|
290
|
+
echo "<lifecycle_rule>"
|
|
291
|
+
grep -v '^<!-- ' "$RULES_DIR/agent-lifecycle-messages.md" | head -50
|
|
292
|
+
echo "</lifecycle_rule>"
|
|
293
|
+
fi
|
|
294
|
+
if [[ -f "$RULES_DIR/context-passing-protocol.md" ]]; then
|
|
295
|
+
echo ""
|
|
296
|
+
echo "<context_protocol_rule>"
|
|
297
|
+
grep -v '^<!-- ' "$RULES_DIR/context-passing-protocol.md" | head -40
|
|
298
|
+
echo "</context_protocol_rule>"
|
|
299
|
+
fi
|
|
300
|
+
fi
|
|
File without changes
|