Initial commit
This commit is contained in:
108
hooks/skill-activation-prompt/README.md
Normal file
108
hooks/skill-activation-prompt/README.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# skill-activation-prompt Hook
|
||||
|
||||
**Purpose:** Automatically suggests relevant skills based on user prompts and file context
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Reads `skill-rules.json` to understand skill trigger patterns
|
||||
2. Matches user prompts against configured patterns
|
||||
3. Checks which files the user is working with
|
||||
4. Injects skill suggestions into Claude's context automatically
|
||||
|
||||
## Why This Hook Is Essential
|
||||
|
||||
This hook makes skill auto-activation work seamlessly. Instead of manually invoking skills, Claude automatically recognizes when a skill would be relevant and suggests it to you.
|
||||
|
||||
## Installation
|
||||
|
||||
### Step 1: Copy Hook Files
|
||||
|
||||
Copy both the shell script and TypeScript implementation to your project's hooks directory:
|
||||
|
||||
```bash
|
||||
# Copy both files from the claudecode-toolkit hooks folder
|
||||
cp skill-activation-prompt.sh your-project/.claude/hooks/
|
||||
cp skill-activation-prompt.ts your-project/.claude/hooks/
|
||||
|
||||
# Make the shell script executable
|
||||
chmod +x your-project/.claude/hooks/skill-activation-prompt.sh
|
||||
```
|
||||
|
||||
### Step 2: Install Dependencies
|
||||
|
||||
Navigate to your hooks directory and install dependencies:
|
||||
|
||||
```bash
|
||||
cd your-project/.claude/hooks
|
||||
npm install
|
||||
```
|
||||
|
||||
### Step 3: Configure Claude Code
|
||||
|
||||
Add the hook to your `.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-activation-prompt.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The hook looks for a `skill-rules.json` file in your `.claude/hooks` directory to understand which skills to suggest. This file defines patterns that trigger skill suggestions.
|
||||
|
||||
**Example skill-rules.json:**
|
||||
|
||||
```json
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"patterns": ["spreadsheet", "excel", "csv", "xlsx"],
|
||||
"skills": ["xlsx"],
|
||||
"filePatterns": ["**/*.xlsx", "**/*.csv"]
|
||||
},
|
||||
{
|
||||
"patterns": ["pdf", "document", "ocr"],
|
||||
"skills": ["pdf-processing-pro"],
|
||||
"filePatterns": ["**/*.pdf"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## How the Hook Triggers
|
||||
|
||||
The hook activates on the `UserPromptSubmit` event, which occurs every time you submit a prompt to Claude. It:
|
||||
|
||||
1. Analyzes your prompt text for relevant keywords
|
||||
2. Checks your current project files for context
|
||||
3. Matches against configured rules
|
||||
4. Suggests applicable skills before Claude processes your request
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Hook not triggering:**
|
||||
- Verify the shell script is executable: `chmod +x skill-activation-prompt.sh`
|
||||
- Check settings.json is properly formatted
|
||||
- Ensure `skill-rules.json` exists in `.claude/hooks/`
|
||||
|
||||
**npm install fails:**
|
||||
- Make sure you're in the `.claude/hooks/` directory
|
||||
- Verify Node.js is installed: `node --version`
|
||||
- Try clearing npm cache: `npm cache clean --force`
|
||||
|
||||
**Skills not being suggested:**
|
||||
- Review `skill-rules.json` patterns
|
||||
- Check that skill names match installed skills
|
||||
- Verify file patterns match your project structure
|
||||
15
hooks/skill-activation-prompt/package.json
Normal file
15
hooks/skill-activation-prompt/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "skill-activation-prompt-hook",
|
||||
"version": "1.0.0",
|
||||
"description": "Claude Code hook that automatically suggests relevant skills based on user prompts and file context",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
5
hooks/skill-activation-prompt/skill-activation-prompt.sh
Executable file
5
hooks/skill-activation-prompt/skill-activation-prompt.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd "$CLAUDE_PROJECT_DIR/.claude/hooks"
|
||||
cat | npx tsx skill-activation-prompt.ts
|
||||
132
hooks/skill-activation-prompt/skill-activation-prompt.ts
Normal file
132
hooks/skill-activation-prompt/skill-activation-prompt.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
interface HookInput {
|
||||
session_id: string;
|
||||
transcript_path: string;
|
||||
cwd: string;
|
||||
permission_mode: string;
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
interface PromptTriggers {
|
||||
keywords?: string[];
|
||||
intentPatterns?: string[];
|
||||
}
|
||||
|
||||
interface SkillRule {
|
||||
type: 'guardrail' | 'domain';
|
||||
enforcement: 'block' | 'suggest' | 'warn';
|
||||
priority: 'critical' | 'high' | 'medium' | 'low';
|
||||
promptTriggers?: PromptTriggers;
|
||||
}
|
||||
|
||||
interface SkillRules {
|
||||
version: string;
|
||||
skills: Record<string, SkillRule>;
|
||||
}
|
||||
|
||||
interface MatchedSkill {
|
||||
name: string;
|
||||
matchType: 'keyword' | 'intent';
|
||||
config: SkillRule;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Read input from stdin
|
||||
const input = readFileSync(0, 'utf-8');
|
||||
const data: HookInput = JSON.parse(input);
|
||||
const prompt = data.prompt.toLowerCase();
|
||||
|
||||
// Load skill rules
|
||||
const projectDir = process.env.CLAUDE_PROJECT_DIR || '$HOME/project';
|
||||
const rulesPath = join(projectDir, '.claude', 'skills', 'skill-rules.json');
|
||||
const rules: SkillRules = JSON.parse(readFileSync(rulesPath, 'utf-8'));
|
||||
|
||||
const matchedSkills: MatchedSkill[] = [];
|
||||
|
||||
// Check each skill for matches
|
||||
for (const [skillName, config] of Object.entries(rules.skills)) {
|
||||
const triggers = config.promptTriggers;
|
||||
if (!triggers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keyword matching
|
||||
if (triggers.keywords) {
|
||||
const keywordMatch = triggers.keywords.some(kw =>
|
||||
prompt.includes(kw.toLowerCase())
|
||||
);
|
||||
if (keywordMatch) {
|
||||
matchedSkills.push({ name: skillName, matchType: 'keyword', config });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Intent pattern matching
|
||||
if (triggers.intentPatterns) {
|
||||
const intentMatch = triggers.intentPatterns.some(pattern => {
|
||||
const regex = new RegExp(pattern, 'i');
|
||||
return regex.test(prompt);
|
||||
});
|
||||
if (intentMatch) {
|
||||
matchedSkills.push({ name: skillName, matchType: 'intent', config });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate output if matches found
|
||||
if (matchedSkills.length > 0) {
|
||||
let output = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
||||
output += '🎯 SKILL ACTIVATION CHECK\n';
|
||||
output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n';
|
||||
|
||||
// Group by priority
|
||||
const critical = matchedSkills.filter(s => s.config.priority === 'critical');
|
||||
const high = matchedSkills.filter(s => s.config.priority === 'high');
|
||||
const medium = matchedSkills.filter(s => s.config.priority === 'medium');
|
||||
const low = matchedSkills.filter(s => s.config.priority === 'low');
|
||||
|
||||
if (critical.length > 0) {
|
||||
output += '⚠️ CRITICAL SKILLS (REQUIRED):\n';
|
||||
critical.forEach(s => output += ` → ${s.name}\n`);
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
if (high.length > 0) {
|
||||
output += '📚 RECOMMENDED SKILLS:\n';
|
||||
high.forEach(s => output += ` → ${s.name}\n`);
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
if (medium.length > 0) {
|
||||
output += '💡 SUGGESTED SKILLS:\n';
|
||||
medium.forEach(s => output += ` → ${s.name}\n`);
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
if (low.length > 0) {
|
||||
output += '📌 OPTIONAL SKILLS:\n';
|
||||
low.forEach(s => output += ` → ${s.name}\n`);
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
output += 'ACTION: Use Skill tool BEFORE responding\n';
|
||||
output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
||||
|
||||
console.log(output);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.error('Error in skill-activation-prompt hook:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Uncaught error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
19
hooks/skill-activation-prompt/tsconfig.json
Normal file
19
hooks/skill-activation-prompt/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user