@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.
Files changed (47) hide show
  1. package/bin/cli.js +338 -0
  2. package/index.js +12 -0
  3. package/package.json +45 -0
  4. package/templates/CLAUDE.md +20 -0
  5. package/templates/agents/10x-simplifier.md +62 -0
  6. package/templates/agents/accessibility-auditor.md +60 -0
  7. package/templates/agents/api-contract-guardian.md +65 -0
  8. package/templates/agents/assumption-challenger.md +52 -0
  9. package/templates/agents/breaking-change-predictor.md +62 -0
  10. package/templates/agents/context-curator.md +53 -0
  11. package/templates/agents/context-loader.md +112 -0
  12. package/templates/agents/decision-logger.md +68 -0
  13. package/templates/agents/dependency-detective.md +58 -0
  14. package/templates/agents/devils-advocate.md +65 -0
  15. package/templates/agents/error-boundary-designer.md +65 -0
  16. package/templates/agents/future-you.md +63 -0
  17. package/templates/agents/incident-replayer.md +62 -0
  18. package/templates/agents/intent-clarifier.md +45 -0
  19. package/templates/agents/legacy-archaeologist.md +54 -0
  20. package/templates/agents/migration-planner.md +61 -0
  21. package/templates/agents/package-checker.md +33 -0
  22. package/templates/agents/performance-profiler.md +61 -0
  23. package/templates/agents/pr-narrator.md +49 -0
  24. package/templates/agents/pre-code-check.md +48 -0
  25. package/templates/agents/refactor-scope-limiter.md +54 -0
  26. package/templates/agents/rubber-duck.md +55 -0
  27. package/templates/agents/scope-creep-detector.md +61 -0
  28. package/templates/agents/session-handoff.md +57 -0
  29. package/templates/agents/structure-validator.md +43 -0
  30. package/templates/agents/test-gap-finder.md +71 -0
  31. package/templates/commands/commit.md +9 -0
  32. package/templates/commands/review.md +12 -0
  33. package/templates/commands/test.md +11 -0
  34. package/templates/hooks/session-start.sh +108 -0
  35. package/templates/hooks/smart-inject-llm.sh +267 -0
  36. package/templates/hooks/smart-inject-rules.sh +300 -0
  37. package/templates/hooks/triggers.d/.gitkeep +0 -0
  38. package/templates/hooks/triggers.json +174 -0
  39. package/templates/rules/agent-lifecycle-messages.md +77 -0
  40. package/templates/rules/announce-actions.md +58 -0
  41. package/templates/rules/code-standards.md +74 -0
  42. package/templates/rules/command-format.md +60 -0
  43. package/templates/rules/constructive-pushback.md +62 -0
  44. package/templates/rules/context-passing-protocol.md +75 -0
  45. package/templates/rules/rule-format.md +72 -0
  46. package/templates/rules/subagent-format.md +94 -0
  47. 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