
Hook Creator
- 63 installs
- 36 repo stars
- Updated July 14, 2026
- oimiragieo/agent-studio
Helps with ai & agent building tasks.
About
hook-creator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- hook-creator
- AI & Agent Building
- AI-coding skill
Hook Creator by the numbers
- 63 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #6,243 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oimiragieo/agent-studio --skill hook-creatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 63 |
|---|---|
| repo stars | ★ 36 |
| Last updated | July 14, 2026 |
| Repository | oimiragieo/agent-studio ↗ |
What it does
Helps with ai & agent building tasks.
Files
Hook Creator Skill
Creates, validates, and registers hooks for the multi-agent orchestration framework.
ROUTER UPDATE REQUIRED (CRITICAL - DO NOT SKIP)
After creating ANY hook, you MUST update documentation:
1. Add to .claude/hooks/README.md under appropriate category
2. Register in config.yaml or settings.json if required
3. Update learnings.md with hook summaryVerification:
grep "<hook-name>" .claude/hooks/README.md || echo "ERROR: hooks/README.md NOT UPDATED!"WHY: Hooks not documented are invisible and unmaintainable.
---
Overview
This skill creates hooks for the Claude Code framework:
- Pre-tool execution - Safety validation before commands run
- Post-tool execution - Logging, memory updates, telemetry
- Session lifecycle - Initialize context, cleanup on exit
- Memory management - Auto-extract learnings, format memory files
- Routing enforcement - Ensure Router-First protocol compliance
Hook Types
| Type | Location | Purpose | When Triggered |
|---|---|---|---|
| Safety | .claude/hooks/safety/ | Validate commands, block dangerous ops | Before Bash/Write/Edit |
| Memory | .claude/hooks/memory/ | Auto-update learnings, extract insights | After task completion |
| Routing | .claude/hooks/routing/ | Enforce router-first protocol | On UserPromptSubmit |
| Session | .claude/hooks/session/ | Initialize/cleanup sessions | Session start/end |
Hook-Agent Archetype Reference
When creating hooks, determine which agent archetypes will be governed by the new hook. See .claude/docs/@HOOK_AGENT_MAP.md for:
- Section 1: Full hook-agent matrix
- Section 2: Archetype hook sets (Router, Implementer, Reviewer, Documenter, Orchestrator, Researcher)
After creating a hook, you MUST add it to both the matrix AND update affected agents' ## Enforcement Hooks sections.
Claude Code Hook Types
| Hook Event | When Triggered | Use Case |
|---|---|---|
PreToolUse | Before tool executes | Validation, blocking, permission checks |
PostToolUse | After tool completes | Logging, cleanup, notifications |
UserPromptSubmit | Before model sees message | Routing, intent analysis, filtering |
Workflow Steps
Step 0: Existence Check and Updater Delegation (MANDATORY - FIRST STEP)
BEFORE creating any hook file, check if it already exists:
1. Check if hook already exists:
test -f .claude/hooks/<category>/<hook-name>.cjs && echo "EXISTS" || echo "NEW"2. If hook EXISTS:
- DO NOT proceed with creation
- Invoke artifact-updater workflow instead:
Skill({
skill: 'artifact-updater',
args: '--type hook --path .claude/hooks/<category>/<hook-name>.cjs --changes "<description of requested changes>"',
});- Return updater result and STOP
3. If hook is NEW:
- Continue with Step 0.5 below
---
Step 0.1: Smart Duplicate Detection (MANDATORY)
Before proceeding with creation, run the 3-layer duplicate check:
const { checkDuplicate } = require('.claude/lib/creation/duplicate-detector.cjs');
const result = checkDuplicate({
artifactType: 'hook',
name: proposedName,
description: proposedDescription,
keywords: proposedKeywords || [],
});Handle results:
- `EXACT_MATCH`: Stop creation. Route to
hook-updaterskill instead:Skill({ skill: 'hook-updater' }) - `REGISTRY_MATCH`: Warn user — artifact is registered but file may be missing. Investigate before creating. Ask user to confirm.
- `SIMILAR_FOUND`: Display candidates with scores. Ask user: "Similar artifact(s) exist. Continue with new creation or update existing?"
- `NO_MATCH`: Proceed to Step 0.5 (companion check).
Override: If user explicitly passes --force, skip this check entirely.
---
Step 0.5: Companion Check
Before proceeding with creation, run the ecosystem companion check:
1. Use companion-check.cjs from .claude/lib/creators/companion-check.cjs 2. Call checkCompanions("hook", "{hook-name}") to identify companion artifacts 3. Review the companion checklist — note which required/recommended companions are missing 4. Plan to create or verify missing companions after this artifact is complete 5. Include companion findings in post-creation integration notes
This step is informational (does not block creation) but ensures the full artifact ecosystem is considered.
---
Reference Hook
Use `.claude/lib/routing/routing-table.cjs` as the canonical routing reference.
Before finalizing any hook, compare against routing-table structure:
- [ ] Has proper CommonJS exports (module.exports)
- [ ] Exports required functions for hook type (validate, main, etc.)
- [ ] Has comprehensive test file (.test.cjs)
- [ ] Has proper error handling (try/catch, graceful fallbacks)
- [ ] Returns correct response format for hook type
Step 1: Gather Hook Requirements
Before creating a hook, gather:
1. Purpose: What should this hook do? 2. Trigger: When should it run? (pre-tool, post-tool, session event) 3. Target tools: Which tools does it apply to? (Bash, Write, Edit, Read) 4. Behavior: Block operation or warn only? 5. Exit codes: What indicates success/failure?
// Example requirements gathering
{
purpose: "Validate git push commands to prevent force push",
trigger: "pre-tool (Bash)",
target_tools: ["Bash"],
behavior: "block if force push detected",
exit_codes: { 0: "allow", 1: "block" }
}Step 2: Determine Hook Type and Location
| If hook does... | Type | Location |
|---|---|---|
| Validates commands | Safety | .claude/hooks/safety/ |
| Modifies routing | Routing | .claude/hooks/routing/ |
| Updates memory | Memory | .claude/hooks/memory/ |
| Session init/cleanup | Session | .claude/hooks/session/ |
Naming Convention: <action>-<target>.cjs
Examples:
validate-git-force-push.cjsenforce-tdd-workflow.cjsextract-workflow-learnings.cjsmemory-reminder.cjs
Step 3: Generate Hook Code (CJS Format)
All hooks use CommonJS format and follow this template:
'use strict';
/**
* {Hook Name}
*
* Type: {pre|post}-{tool} | session-{start|end} | user-prompt
* Purpose: {One line description}
* Trigger: {When this hook runs}
*
* Exit codes:
* - 0: Allow operation (with optional warning)
* - 1: Block operation (when in blocking mode)
*
* Environment:
* {HOOK_NAME}_MODE=block|warn|off (default: warn unless explicitly required)
*/
const fs = require('fs');
const path = require('path');
// Find project root by looking for .claude directory
function findProjectRoot() {
let dir = __dirname;
while (dir !== path.parse(dir).root) {
if (fs.existsSync(path.join(dir, '.claude'))) {
return dir;
}
dir = path.dirname(dir);
}
return process.cwd();
}
const PROJECT_ROOT = findProjectRoot();
const ENFORCEMENT_MODE = process.env.HOOK_NAME_MODE || 'warn';
/**
* Parse hook input from Claude Code
* Claude Code passes JSON via process.argv[2] for hooks
* @returns {Object|null} Parsed hook input or null
*/
function parseHookInput() {
try {
if (process.argv[2]) {
return JSON.parse(process.argv[2]);
}
} catch (e) {
// Fallback for testing or invalid input
}
return null;
}
/**
* Validate hook - called by Claude Code or programmatically
* @param {Object} context - Hook context with tool info
* @param {string} context.tool - Tool name (Bash, Write, Edit, Read)
* @param {Object} context.parameters - Tool parameters
* @returns {Object} Validation result with valid (boolean), error (string), and optional warning (string)
*/
function validate(context) {
const { tool, parameters } = context;
// YOUR VALIDATION LOGIC HERE
// Return validation result
return { valid: true, error: '' };
}
/**
* Main execution for CLI hook usage
*/
function main() {
// Skip if enforcement is off
if (ENFORCEMENT_MODE === 'off') {
process.exit(0);
}
const hookInput = parseHookInput();
if (!hookInput) {
process.exit(0);
}
// Get tool name and input
const toolName = hookInput.tool_name || hookInput.tool;
const toolInput = hookInput.tool_input || hookInput.input || {};
// Run validation
const result = validate({ tool: toolName, parameters: toolInput });
if (!result.valid) {
if (ENFORCEMENT_MODE === 'block') {
console.error(`BLOCKED: ${result.error}`);
process.exit(1);
} else {
console.warn(`WARNING: ${result.error}`);
process.exit(0);
}
}
if (result.warning) {
console.warn(`WARNING: ${result.warning}`);
}
process.exit(0);
}
// Run main if executed directly
if (require.main === module) {
main();
}
// Export for programmatic use and testing
module.exports = {
validate,
findProjectRoot,
PROJECT_ROOT,
};Step 4: Create Test File
Every hook MUST have a corresponding test file:
'use strict';
const { validate } = require('./hook-name.cjs');
describe('Hook Name', () => {
test('allows valid operations', () => {
const result = validate({
tool: 'Bash',
parameters: { command: 'git status' },
});
expect(result.valid).toBe(true);
expect(result.error).toBe('');
});
test('blocks dangerous operations', () => {
const result = validate({
tool: 'Bash',
parameters: { command: 'git push --force' },
});
expect(result.valid).toBe(false);
expect(result.error).toContain('force push');
});
test('handles missing parameters gracefully', () => {
const result = validate({
tool: 'Bash',
parameters: {},
});
expect(result.valid).toBe(true);
});
});Step 5: Register Hook (If Needed)
Some hooks require registration in config files:
For pre/post-tool hooks (settings.json):
{
"hooks": {
"pre-tool": [".claude/hooks/safety/hook-name.cjs"],
"post-tool": [".claude/hooks/memory/hook-name.cjs"]
}
}For event hooks (config.yaml):
hooks:
UserPromptSubmit:
- path: .claude/hooks/routing/hook-name.cjs
type: command
SessionStart:
- path: .claude/hooks/session/hook-name.cjs
type: commandStep 6: Update Documentation (MANDATORY - BLOCKING)
After creating a hook, update .claude/hooks/README.md:
#### {Hook Name} (`hook-name.cjs`)
{Description of what the hook does}
**When it runs:** {Trigger condition}
**What it checks/does:** {Detailed behavior}Verify with:
grep "hook-name" .claude/hooks/README.md || echo "ERROR: Not documented!"Step 7: System Impact Analysis (MANDATORY)
This analysis is MANDATORY. Hook creation is INCOMPLETE without it.
After creating a hook:
1. Settings Registration (BLOCKING)
- Add to .claude/settings.json PreToolUse/PostToolUse/etc.
- Verify with:
grep "hook-name" .claude/settings.json
2. Test Coverage (BLOCKING)
- Create .test.cjs file with minimum 10 test cases
- Run tests:
node .claude/hooks/<category>/<name>.test.cjs
3. Documentation
- Update .claude/docs/ if hook adds new capability
- Add usage examples
4. Related Hooks
- Check if hook affects other hooks in same trigger category
- Document interaction patterns
Full Checklist:
[HOOK-CREATOR] System Impact Analysis for: <hook-name>
1. HOOK FILE CREATED
[ ] Created at .claude/hooks/<type>/<hook-name>.cjs
[ ] Follows CJS format with validate() export
[ ] Has main() function for CLI execution
[ ] Handles graceful degradation (warn by default)
2. TEST FILE CREATED (minimum 10 test cases)
[ ] Created at .claude/hooks/<type>/<hook-name>.test.cjs
[ ] Tests valid operations (3+ cases)
[ ] Tests blocked operations (3+ cases)
[ ] Tests edge cases (3+ cases)
[ ] Tests error handling (1+ cases)
3. DOCUMENTATION UPDATED
[ ] Added to .claude/hooks/README.md
[ ] Documented trigger conditions
[ ] Documented exit codes
4. REGISTRATION (BLOCKING)
[ ] Added to settings.json (pre/post-tool hooks)
[ ] Added to config.yaml (event hooks)
[ ] Verified: grep "<hook-name>" .claude/settings.json
5. MEMORY UPDATED
[ ] Added to learnings.md with hook summary
6. HOOK-AGENT MAP UPDATED (MANDATORY)
[ ] Added new hook to @HOOK_AGENT_MAP.md Section 1 matrix
[ ] Determined which agent archetypes are affected (based on hook trigger/tool target)
[ ] Updated affected agents' `## Enforcement Hooks` sections
[ ] Verified: `grep "<hook-name>" .claude/docs/@HOOK_AGENT_MAP.md || echo "ERROR: Hook not in agent map!"`BLOCKING: If ANY item above is missing, hook creation is INCOMPLETE.
Step 8: Post-Creation Hook Registration (Phase 1 Integration)
This step is CRITICAL. After creating the hook artifact, you MUST register it in the hook discovery system.
Phase 1 Context: Phase 1 is responsible for tool and hook validation/discovery. Hooks created without registration are invisible to the system and will not be loaded at startup.
After hook file is written and tested:
1. Create/Update Hook Registry Entry in appropriate location:
If registry doesn't exist, create .claude/context/artifacts/hook-registry.json:
{
"hooks": [
{
"name": "{hook-name}",
"id": "{hook-name}",
"description": "{Brief description from hook}",
"category": "{safety|routing|memory|session|validation|audit}",
"type": "{pre-tool|post-tool|user-prompt|session-start|session-end}",
"version": "1.0.0",
"targetTools": ["{Tool1}", "{Tool2}"],
"enforcementMode": "{block|warn|off}",
"defaultEnabled": true,
"filePath": ".claude/hooks/{category}/{hook-name}.cjs",
"testFilePath": ".claude/hooks/{category}/{hook-name}.test.cjs",
"environmentVariable": "{HOOK_NAME}_MODE"
}
]
}2. Validate Hook Against Schema:
Ensure hook validates against .claude/schemas/hook-schema.json (if exists):
# Validate hook structure
node -e "
const hook = require('./.claude/hooks/{category}/{hook-name}.cjs');
if (hook.validate) console.log('✓ Has validate() export');
if (hook.PROJECT_ROOT) console.log('✓ Has PROJECT_ROOT');
if (hook.findProjectRoot) console.log('✓ Has findProjectRoot()');
"3. Register Hook in Loader:
Update .claude/lib/hooks/hook-loader.cjs (if exists) to include new hook:
const HOOKS_MANIFEST = {
'{hook-name}': {
path: './.claude/hooks/{category}/{hook-name}.cjs',
type: '{pre-tool|post-tool}',
matcher: '{Bash|Write|Edit|Read}', // Optional
enabled: true,
enforcementMode: process.env.{HOOK_NAME}_MODE || 'warn'
}
};4. Register Hook in Configuration:
For pre/post-tool hooks - Update .claude/settings.json:
{
"hooks": {
"pre-tool": ["./.claude/hooks/safety/{hook-name}.cjs"],
"post-tool": ["./.claude/hooks/memory/{hook-name}.cjs"]
}
}For event hooks - Update .claude/config.yaml:
hooks:
UserPromptSubmit:
- path: ./.claude/hooks/routing/{hook-name}.cjs
type: command
SessionStart:
- path: ./.claude/hooks/session/{hook-name}.cjs
type: command5. Document in `.claude/hooks/README.md`:
Add entry under appropriate category:
#### {Hook Name} (`{hook-name}.cjs`)
{Detailed description of what the hook does.}
**When it runs:** {Trigger condition - e.g., "Before every Bash command", "After task completion"}
**What it checks/does:**
- {Check/action 1}
- {Check/action 2}
- {Check/action 3}
**Enforcement mode:** `process.env.{HOOK_NAME}_MODE` (default: `warn`)
**Test file:** `.claude/hooks/{category}/{hook-name}.test.cjs`
**Related hooks:** {List any hooks that interact with this one}6. Update Memory:
Append to .claude/context/memory/learnings.md:
## Hook: {hook-name}
- **Type:** {pre-tool|post-tool|event}
- **Category:** {safety|routing|memory|session|validation}
- **Purpose:** {Detailed purpose}
- **Trigger:** {When it runs}
- **Enforcement:** {Block/warn/off by default}
- **Integration Notes:** {Any special considerations}Why this matters: Without hook registration:
- Hooks are not loaded at startup
- Hook validation doesn't occur
- System cannot discover available hooks
- Safety validators are bypassed
- "Invisible artifact" pattern emerges
Phase 1 Integration: Hook registry is the discovery mechanism for Phase 1, enabling the system to validate hooks against schema, load them at startup, and enforce safety rules consistently.
Step 9: Integration Verification (BLOCKING - DO NOT SKIP)
This step verifies the artifact is properly integrated into the ecosystem.
Before calling TaskUpdate({ status: "completed" }), you MUST run the Post-Creation Validation workflow:
1. Run the 10-item integration checklist:
node .claude/tools/cli/validate-integration.cjs .claude/hooks/<category>/<hook-name>.cjs2. Verify exit code is 0 (all checks passed)
3. If exit code is 1 (one or more checks failed):
- Read the error output for specific failures
- Fix each failure:
- Missing hook registry -> Create registry entry (Step 8)
- Missing settings.json entry -> Register hook (Step 8)
- Missing documentation -> Add to hooks/README.md
- Missing memory update -> Update learnings.md
- Missing test file -> Create .test.cjs file
- Re-run validation until exit code is 0
4. Only proceed when validation passes
This step is BLOCKING. Do NOT mark task complete until validation passes.
Why this matters: The Party Mode incident showed that fully-implemented artifacts can be invisible to the Router if integration steps are missed. This validation ensures no "invisible artifact" pattern.
Reference: .claude/workflows/core/post-creation-validation.md
---
CLI Reference
# Create hook using CLI tool
node .claude/tools/hook-creator/create-hook.mjs \
--name "hook-name" \
--type "PreToolUse|PostToolUse|UserPromptSubmit" \
--purpose "Description of what the hook does" \
--category "safety|routing|memory|audit|security|validation|custom" \
--matcher "Edit|Write|Bash" # Optional: tool matcher regex
# List all hooks
node .claude/tools/hook-creator/create-hook.mjs --list
# Validate hook structure
node .claude/tools/hook-creator/create-hook.mjs --validate "<path>"
# Assign to agents
node .claude/tools/hook-creator/create-hook.mjs --assign "name" --agents "agent1,agent2"
# Unregister hook
node .claude/tools/hook-creator/create-hook.mjs --unregister "<path>"
# Test with sample input
echo '{"tool_name":"Edit","tool_input":{"file_path":"test.js"} }' | node .claude/hooks/<category>/<hook-name>.cjs---
Hook Patterns Reference
Pattern 1: Safety Validator (Pre-Tool)
For validating commands before execution:
'use strict';
/**
* Validate Git Force Push
* Prevents accidental force pushes to protected branches
*/
const PROTECTED_BRANCHES = ['main', 'master', 'production'];
function validate(context) {
const { tool, parameters } = context;
if (tool !== 'Bash') {
return { valid: true, error: '' };
}
const command = parameters?.command || '';
// Check for force push
if (command.includes('git push') && (command.includes('--force') || command.includes('-f'))) {
// Check if pushing to protected branch
for (const branch of PROTECTED_BRANCHES) {
if (command.includes(branch)) {
return {
valid: false,
error: `Force push to ${branch} blocked. Use --force-with-lease instead.`,
};
}
}
return {
valid: true,
error: '',
warning: 'Force push detected. Ensure you know what you are doing.',
};
}
return { valid: true, error: '' };
}
module.exports = { validate };Pattern 2: Memory Extractor (Post-Tool)
For extracting learnings after task completion:
'use strict';
/**
* Extract Workflow Learnings
* Automatically captures patterns from completed workflows
*/
const fs = require('fs');
const path = require('path');
function findProjectRoot() {
let dir = __dirname;
while (dir !== path.parse(dir).root) {
if (fs.existsSync(path.join(dir, '.claude'))) return dir;
dir = path.dirname(dir);
}
return process.cwd();
}
const LEARNINGS_PATH = path.join(findProjectRoot(), '.claude/context/memory/learnings.md');
function extractLearnings(context) {
const { tool, parameters, result } = context;
// Only process completed tasks
if (!result || result.status !== 'completed') {
return { extracted: false };
}
// Extract patterns from result
const learnings = [];
if (result.patterns) {
learnings.push(...result.patterns);
}
if (result.decisions) {
learnings.push(...result.decisions);
}
if (learnings.length === 0) {
return { extracted: false };
}
// Append to learnings file
const entry = `\n## [${new Date().toISOString().split('T')[0]}] ${context.taskName || 'Task'}\n\n`;
const content = learnings.map(l => `- ${l}`).join('\n');
fs.appendFileSync(LEARNINGS_PATH, entry + content + '\n');
return { extracted: true, count: learnings.length };
}
module.exports = { extractLearnings };Pattern 3: Routing Enforcer (User Prompt)
For enforcing routing protocols:
'use strict';
/**
* Router First Enforcer
* Ensures all requests go through the Router agent
*/
function validate(context) {
const { prompt, currentAgent } = context;
// Skip if already routed
if (currentAgent === 'router') {
return { valid: true, error: '' };
}
// Skip slash commands (handled by skill system)
if (prompt && prompt.trim().startsWith('/')) {
return { valid: true, error: '' };
}
// Suggest routing
return {
valid: true,
error: '',
warning: 'Consider using Router to spawn appropriate agent via Task tool.',
};
}
module.exports = { validate };Pattern 4: Session Initializer
For session lifecycle management:
'use strict';
/**
* Session Memory Initializer
* Reminds agents to read memory files at session start
*/
const fs = require('fs');
const path = require('path');
function findProjectRoot() {
let dir = __dirname;
while (dir !== path.parse(dir).root) {
if (fs.existsSync(path.join(dir, '.claude'))) return dir;
dir = path.dirname(dir);
}
return process.cwd();
}
const MEMORY_FILES = [
'.claude/context/memory/learnings.md',
'.claude/context/memory/issues.md',
'.claude/context/memory/decisions.md',
];
function initialize() {
const root = findProjectRoot();
console.log('\n' + '='.repeat(50));
console.log(' SESSION MEMORY REMINDER');
console.log('='.repeat(50));
console.log('\n Before starting work, read these memory files:');
for (const file of MEMORY_FILES) {
const fullPath = path.join(root, file);
if (fs.existsSync(fullPath)) {
const stats = fs.statSync(fullPath);
const modified = stats.mtime.toISOString().split('T')[0];
console.log(` - ${file} (updated: ${modified})`);
}
}
console.log('\n' + '='.repeat(50) + '\n');
return { initialized: true };
}
// Run on direct execution
if (require.main === module) {
initialize();
}
module.exports = { initialize };---
Examples
Security Validation Hook
node .claude/tools/hook-creator/create-hook.mjs \
--name "secret-detector" \
--type "PreToolUse" \
--purpose "Blocks commits containing secrets or credentials" \
--category "security" \
--matcher "Bash"Audit Logging Hook
node .claude/tools/hook-creator/create-hook.mjs \
--name "operation-logger" \
--type "PostToolUse" \
--purpose "Logs all file modifications to audit trail" \
--category "audit"Intent Analysis Hook
node .claude/tools/hook-creator/create-hook.mjs \
--name "intent-classifier" \
--type "UserPromptSubmit" \
--purpose "Classifies user intent for intelligent routing" \
--category "routing"---
Workflow Integration
This skill is part of the unified artifact lifecycle. For complete multi-agent orchestration:
Router Decision: .claude/workflows/core/router-decision.md
- How the Router discovers and invokes this skill's artifacts
Artifact Lifecycle: .claude/workflows/core/skill-lifecycle.md
- Discovery, creation, update, deprecation phases
- Version management and registry updates
- CLAUDE.md integration requirements
External Integration: .claude/workflows/core/external-integration.md
- Safe integration of external artifacts
- Security review and validation phases
---
Cross-Reference: Creator Ecosystem
This skill is part of the Creator Ecosystem. After creating a hook, consider if companion artifacts are needed:
| Gap Discovered | Required Artifact | Creator to Invoke | When |
|---|---|---|---|
| Domain knowledge needs a reusable skill | skill | Skill({ skill: 'skill-creator' }) | Gap is a full skill domain |
| Existing skill has incomplete coverage | skill update | Skill({ skill: 'skill-updater' }) | Close skill exists but incomplete |
| Capability needs a dedicated agent | agent | Skill({ skill: 'agent-creator' }) | Agent to own the capability |
| Existing agent needs capability update | agent update | Skill({ skill: 'agent-updater' }) | Close agent exists but incomplete |
| Domain needs code/project scaffolding | template | Skill({ skill: 'template-creator' }) | Reusable code patterns needed |
| Behavior needs pre/post execution guards | hook | Skill({ skill: 'hook-creator' }) | Enforcement behavior required |
| Process needs multi-phase orchestration | workflow | Skill({ skill: 'workflow-creator' }) | Multi-step coordination needed |
| Artifact needs structured I/O validation | schema | Skill({ skill: 'schema-creator' }) | JSON schema for artifact I/O |
| User interaction needs a slash command | command | Skill({ skill: 'command-creator' }) | User-facing shortcut needed |
| Repeated logic needs a reusable CLI tool | tool | Skill({ skill: 'tool-creator' }) | CLI utility needed |
| Narrow/single-artifact capability only | inline | Document within this artifact only | Too specific to generalize |
Integration Workflow
After creating a hook that needs additional capabilities:
// 1. Hook created but needs dedicated skill
Skill({ skill: 'skill-creator' });
// Create skill that encapsulates hook logic
// 2. Hook needs to be assigned to agent
// Update agent's workflow to include hook awareness
// 3. Hook needs workflow for testing
// Create workflow in .claude/workflows/<hook-name>-test-workflow.mdPost-Creation Checklist for Ecosystem Integration
After hook is fully created and validated:
[ ] Does hook need a skill wrapper? -> Use skill-creator
[ ] Does hook need dedicated agent? -> Use agent-creator
[ ] Does hook need testing workflow? -> Create workflow
[ ] Should hook be enabled by default? -> Update config.yaml
[ ] Does hook interact with other hooks? -> Document in README.md
[ ] Run post-creation validation -> node .claude/tools/cli/validate-integration.cjs .claude/hooks/<category>/<hook-name>.cjs---
Iron Laws of Hook Creation
These rules are INVIOLABLE. Breaking them causes silent failures.
1. NO HOOK WITHOUT validate() EXPORT
- Every hook MUST export validate() function
- Hooks without validate() cannot be called programmatically
2. NO HOOK WITHOUT main() FOR CLI
- Every hook MUST have main() for CLI execution
- Run only when require.main === module
3. NO HOOK WITHOUT ENFORCEMENT CONTROLS
- Support 'block|warn|off' via environment variable
- Default to 'warn' unless explicitly required to block
- Never crash on malformed input
4. NO HOOK WITHOUT ERROR HANDLING
- Wrap JSON.parse in try/catch
- Handle missing parameters gracefully
- Return valid: true when unsure (fail open, not closed)
5. NO HOOK WITHOUT TEST FILE
- Every hook needs <hook-name>.test.cjs
- Test valid, invalid, and edge cases
6. NO HOOK WITHOUT DOCUMENTATION
- Add to .claude/hooks/README.md
- Document trigger, behavior, exit codes
7. CROSS-PLATFORM PATHS
- Use path.join() not string concatenation
- Handle both / and \ path separators
- Use path.normalize() for comparison
8. NO CREATION WITHOUT SYSTEM IMPACT ANALYSIS
- Check if hook requires settings.json registration
- Check if hook requires config.yaml registration
- Update @HOOK_AGENT_MAP.md with new hook row (MANDATORY)
- Update affected agents' Enforcement Hooks sections (MANDATORY)
- Check if related hooks need updating
- Document all system changes made
9. IRON LAW I: PRE-TOOL HOOKS MUST VALIDATE AGAINST JSON SCHEMA
- PreToolUse hooks MUST compile and validate input against the companion
schemas/input.schema.json using AJV or equivalent before allowing execution
- Pattern: compile(schema) → validate(input) → exit 2 on schema failure
- Never block (exit 2) on schema-load errors — fail open, not closed
- Search: site:github.com "preToolUse" "ajv" "validate" filetype:cjs
10. IRON LAW III: POST-TOOL HOOKS MUST EMIT OBSERVABILITY EVENTS
- PostToolUse hooks MUST append a structured JSON line to
.claude/context/runtime/tool-events.jsonl via the centralized emitter:
const { sendEvent } = require('.claude/tools/observability/send-event.cjs')
- Required fields: tool_name, agent_id, session_id, outcome, timestamp
- Never crash on emit failure (try/catch, fail open)
- Inspect events: node .claude/tools/observability/send-event.cjs --tail 20---
Integration Points
- Ecosystem Assessor: Hook creator integrates with ecosystem assessment for reverse lookups
- Agent Creator: Agents can reference hooks in their frontmatter
- Skill Creator: Skills can define hooks in their hooks/ directory
- Settings.json: Hooks are auto-registered with proper triggers and matchers
- Config.yaml: Event hooks registered for UserPromptSubmit, SessionStart, etc.
---
Architecture Compliance
File Placement (ADR-076)
- Hooks:
.claude/hooks/{category}/(safety, routing, memory, session, validation, audit) - Hook tests:
.claude/hooks/{category}/{name}.test.cjs(co-located with hooks) - Tests:
tests/(integration tests for hooks) - Related docs:
.claude/docs/ - Hook registry:
.claude/hooks/README.md
Documentation References (CLAUDE.md v3.1.0)
- Reference files use @notation: @ENFORCEMENT_HOOKS.md, @TOOL_REFERENCE.md
- Located in:
.claude/docs/@*.md - See: CLAUDE.md Section 1.3 (ENFORCEMENT HOOKS reference)
Shell Security (ADR-077)
- NEW SAFETY HOOKS: bash-cwd-validator.cjs, shell-injection-validator.cjs, variable-quoting-validator.cjs (ADR-077 Phase 2)
- PHASE 3 HOOKS: shellcheck-validator.cjs, command-allowlist-validator.cjs (reference implementations)
- Hook tests MUST validate shell security patterns
- See: .claude/docs/SHELL-SECURITY-GUIDE.md
- Apply to: all safety hooks, pre-tool hooks, validation hooks
Recent ADRs
- ADR-075: Router Config-Aware Model Selection
- ADR-076: File Placement Architecture Redesign
- ADR-077: Shell Command Security Architecture
---
File Placement & Standards
Output Location Rules
This skill outputs to: .claude/hooks/<category>/
Categories:
safety/- Safety validators (command validation, security checks, shell security)routing/- Router enforcement hooksmemory/- Memory management hookssession/- Session lifecycle hooksvalidation/- Input/output validation hooks
Mandatory References
- File Placement: See
.claude/docs/FILE_PLACEMENT_RULES.md - Developer Workflow: See
.claude/docs/DEVELOPER_WORKFLOW.md - Artifact Naming: See
.claude/docs/ARTIFACT_NAMING.md
Enforcement
File placement is enforced by file-placement-guard.cjs hook. Invalid placements will be blocked in production mode.
---
Memory Protocol (MANDATORY)
Before creating a hook:
cat .claude/context/memory/learnings.mdCheck for:
- Previously created hooks
- Known hook patterns
- User preferences for hook behavior
After completing:
- New hook created -> Append to
.claude/context/memory/learnings.md - Issue with hook -> Append to
.claude/context/memory/issues.md - Hook design decision -> Append to
.claude/context/memory/decisions.md
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.
Ecosystem Alignment Contract (MANDATORY)
This creator skill is part of a coordinated creator ecosystem. Any artifact created here must align with and validate against related creators:
agent-creatorfor ownership and execution pathsskill-creatorfor capability packaging and assignmenttool-creatorfor executable automation surfaceshook-creatorfor enforcement and guardrailsrule-creatorandsemgrep-rule-creatorfor policy and static checkstemplate-creatorfor standardized scaffoldsworkflow-creatorfor orchestration and phase gatingcommand-creatorfor user/operator command UX
Cross-Creator Handshake (Required)
Before completion, verify all relevant handshakes:
1. Artifact route exists in .claude/CLAUDE.md and related routing docs. 2. Discovery/registry entries are updated (catalog/index/registry as applicable). 3. Companion artifacts are created or explicitly waived with reason. 4. validate-integration.cjs passes for the created artifact. 5. Skill index is regenerated when skill metadata changes.
Research Gate (Exa + arXiv — BOTH MANDATORY)
For new patterns, templates, or workflows, research is mandatory:
1. Use Exa for implementation and ecosystem patterns:
mcp__Exa__web_search_exa({ query: '<topic> 2025 best practices' })mcp__Exa__get_code_context_exa({ query: '<topic> implementation examples' })
2. Search arXiv for academic research (mandatory for AI/ML, agents, evaluation, orchestration, memory/RAG, security):
- Via Exa:
mcp__Exa__web_search_exa({ query: 'site:arxiv.org <topic> 2024 2025' }) - Direct API:
WebFetch({ url: 'https://arxiv.org/search/?query=<topic>&searchtype=all&start=0' })
3. Record decisions, constraints, and non-goals in artifact references/docs. 4. Keep updates minimal and avoid overengineering.
arXiv is mandatory (not fallback) when topic involves: AI agents, LLM evaluation, orchestration, memory/RAG, security, static analysis, or any emerging methodology.
Regression-Safe Delivery
- Follow strict RED -> GREEN -> REFACTOR for behavior changes.
- Run targeted tests for changed modules.
- Run lint/format on changed files.
- Keep commits scoped by concern (logic/docs/generated artifacts).
Optional: Evaluation Quality Gate
Run the shared evaluation framework to verify hook quality:
node .claude/skills/skill-creator/scripts/eval-runner.cjs --skill hook-creatorGrader assertions for hook artifacts:
- Exit codes correct: Hook exits
0(allow) or2(block) only; exit1is never used (treated as error, not block per SE-03) - 100ms performance budget: Hook body completes in under 100ms; no network calls, no blocking I/O, no long computation in the hot path
- Fail-open vs fail-closed policy: Security hooks (routing, creator, write) are fail-closed (
process.exit(2)on errors); advisory and PostToolUse hooks are fail-open (process.exit(0)on errors) - Graceful error handling: Hook body is wrapped in
try/catch; unexpected errors exit0(non-critical) or2(security-critical) — never crash without an exit code - Registration complete: Hook is registered in
.claude/settings.jsonand documented in@ENFORCEMENT_HOOKS.md
See .claude/skills/skill-creator/EVAL_WORKFLOW.md for full evaluation protocol and grader/analyzer agent usage.
Invoke the hook-creator skill and follow it exactly as presented to you
#!/usr/bin/env node
/**
* hook-creator - Post-Execute Hook
* ==================================
*
* Runs after the hook-creator executes to clean up state.
*
* CRIT-002 FIX: This hook now properly clears the active-creators.json entry
* to ensure the creator state is cleaned up after workflow completion.
*
* State file: .claude/context/runtime/active-creators.json
* Actions:
* 1. Clear this creator's active state
* 2. Log completion status
* 3. Handle both success and failure cases
*/
'use strict';
const fs = require('fs');
const path = require('path');
const { safeParseJSON } = require('../../../lib/utils/safe-json.cjs');
const CREATOR_NAME = 'hook-creator';
// Parse hook input (result from skill execution)
const result = safeParseJSON(process.argv[2] || '{}');
console.log(`[${CREATOR_NAME.toUpperCase()}] Post-execute: Cleaning up state...`);
/**
* Find project root by looking for .claude/CLAUDE.md
* @returns {string} Project root path
*/
function findProjectRoot() {
let dir = __dirname;
while (dir !== path.parse(dir).root) {
if (fs.existsSync(path.join(dir, '.claude', 'CLAUDE.md'))) {
return dir;
}
dir = path.dirname(dir);
}
return process.cwd();
}
const PROJECT_ROOT = findProjectRoot();
const STATE_FILE = path.join(PROJECT_ROOT, '.claude/context/runtime/active-creators.json');
/**
* Clear this creator's active state from the unified state file.
* CRIT-002 FIX: This ensures the creator state is properly cleaned up
* after workflow completion, regardless of success or failure.
*
* @returns {boolean} Success status
*/
function clearCreatorActive() {
try {
if (!fs.existsSync(STATE_FILE)) {
// State file doesn't exist - nothing to clear
console.log(`[${CREATOR_NAME.toUpperCase()}] No state file found - nothing to clear`);
return true;
}
// Read existing state
let state = {};
try {
state = safeParseJSON(fs.readFileSync(STATE_FILE, 'utf8'));
} catch (_e) {
// If file is corrupted, start fresh
state = {};
}
// Clear this creator's active state
if (state[CREATOR_NAME]) {
state[CREATOR_NAME].active = false;
state[CREATOR_NAME].clearedAt = new Date().toISOString();
state[CREATOR_NAME].clearReason = result.success ? 'completed' : 'failed';
}
// Write updated state
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
console.log(`[${CREATOR_NAME.toUpperCase()}] State cleared in: ${STATE_FILE}`);
return true;
} catch (err) {
console.error(`[${CREATOR_NAME.toUpperCase()}] Failed to clear state:`, err.message);
return false;
}
}
/**
* Process execution result and perform cleanup
* @param {Object} executionResult - Result from skill execution
* @returns {{ success: boolean, message?: string }}
*/
function processResult(executionResult) {
// CRIT-002 FIX: Always clear active state, regardless of success/failure
const stateCleared = clearCreatorActive();
if (!stateCleared) {
return {
success: false,
message: 'Failed to clear creator state',
};
}
// Log completion status
if (executionResult.success) {
console.log(
`[${CREATOR_NAME.toUpperCase()}] Hook created successfully: ${executionResult.artifactName || 'unknown'}`
);
} else {
console.warn(
`[${CREATOR_NAME.toUpperCase()}] Hook creation failed: ${executionResult.error || 'unknown error'}`
);
}
return { success: true };
}
// Run post-processing
const outcome = processResult(result);
if (outcome.success) {
console.log(`[${CREATOR_NAME.toUpperCase()}] Post-execute complete`);
process.exit(0);
} else {
console.error(`[${CREATOR_NAME.toUpperCase()}] Post-execute had issues: ${outcome.message}`);
process.exit(0); // Still exit 0 - post-execute failures shouldn't block
}
#!/usr/bin/env node
/**
* hook-creator - Pre-Execute Hook
* Runs before the skill executes to validate input and mark hook-creator as active.
*
* CRITICAL: This hook creates the state file that unified-creator-guard.cjs checks.
* Without this, the guard has no way to know hook-creator was invoked.
*
* State file: .claude/context/runtime/active-creators.json
* Format: { "hook-creator": { "active": true, "invokedAt": "ISO string", "ttl": 600000 } }
*/
const fs = require('fs');
const path = require('path');
const { safeParseJSON } = require('../../../lib/utils/safe-json.cjs');
const CREATOR_NAME = 'hook-creator';
/**
* Default TTL for creator active state (3 minutes)
* CRIT-001 FIX: Aligned with unified-creator-guard.cjs DEFAULT_TTL_MS
* SEC-REMEDIATION-001: Reduced from 10 to 3 minutes to minimize state tampering window
* Configurable via CREATOR_STATE_TTL_MS env var
*/
const DEFAULT_TTL_MS = Number(process.env.CREATOR_STATE_TTL_MS) || 3 * 60 * 1000;
// Parse hook input
const input = safeParseJSON(process.argv[2] || '{}');
console.log(`[${CREATOR_NAME.toUpperCase()}] Pre-execute: Marking ${CREATOR_NAME} as active...`);
/**
* Find project root by looking for .claude/CLAUDE.md
* This is more reliable than just looking for .claude directory
* because there may be nested .claude directories created by tests.
* @returns {string} Project root path
*/
function findProjectRoot() {
let dir = __dirname;
while (dir !== path.parse(dir).root) {
// Check for CLAUDE.md which is unique to project root
if (fs.existsSync(path.join(dir, '.claude', 'CLAUDE.md'))) {
return dir;
}
dir = path.dirname(dir);
}
return process.cwd();
}
const PROJECT_ROOT = findProjectRoot();
const STATE_FILE = path.join(PROJECT_ROOT, '.claude/context/runtime/active-creators.json');
/**
* Mark hook-creator as active by updating unified state file.
* This allows unified-creator-guard.cjs to know that hook writes are legitimate.
*/
function markCreatorActive() {
try {
const stateDir = path.dirname(STATE_FILE);
// Ensure runtime directory exists
if (!fs.existsSync(stateDir)) {
fs.mkdirSync(stateDir, { recursive: true });
}
// Read existing state or create new
let state = {};
if (fs.existsSync(STATE_FILE)) {
try {
state = safeParseJSON(fs.readFileSync(STATE_FILE, 'utf8'));
} catch (_e) {
// If file is corrupted, start fresh
state = {};
}
}
// Update this creator's state
// CRIT-001 FIX: Use DEFAULT_TTL_MS (3 minutes) aligned with unified-creator-guard.cjs
state[CREATOR_NAME] = {
active: true,
invokedAt: new Date().toISOString(),
artifactName: null, // Will be set during workflow
ttl: DEFAULT_TTL_MS,
};
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
console.log(`[${CREATOR_NAME.toUpperCase()}] State file updated at:`, STATE_FILE);
return true;
} catch (err) {
console.error(`[${CREATOR_NAME.toUpperCase()}] Failed to update state file:`, err.message);
return false;
}
}
/**
* Validate input before execution
*/
function validateInput(_input) {
const errors = [];
// Basic validation - hook-creator can work with minimal input
// The actual hook name is typically determined during the workflow
return errors;
}
// Mark hook-creator as active FIRST (critical for unified-creator-guard)
const stateCreated = markCreatorActive();
if (!stateCreated) {
console.warn(
`[${CREATOR_NAME.toUpperCase()}] Warning: Could not create state file. unified-creator-guard may block hook writes.`
);
}
// Run validation
const errors = validateInput(input);
if (errors.length > 0) {
console.error(`[${CREATOR_NAME.toUpperCase()}] Validation failed:`);
errors.forEach(e => console.error(' - ' + e));
process.exit(1);
}
console.log(
`[${CREATOR_NAME.toUpperCase()}] Pre-execute complete. Hook-creator workflow is now active.`
);
process.exit(0);
hook-creator Research Requirements
Generated: 2026-02-28
Skill Description
'Creates and registers hooks for the Claude Code framework. Handles pre/post tool execution, validation, memory, and session hooks. Use when new validation, safety, or automation hooks are needed.'
Research Areas
- Current best practices for hook-creator
- Industry standards and tooling
- Integration patterns
Source References
- To be populated by skill-updater research phase
hook-creator Rules
Purpose
'Creates and registers hooks for the Claude Code framework. Handles pre/post tool execution, validation, memory, and session hooks. Use when new validation, safety, or automation hooks are needed.'
Best Practices
- Always register hooks in appropriate config
- Test hooks before deployment
- Include error handling in all hooks
- Document hook triggers and behavior
- Use explicit enforcement modes (block|warn|off) with env overrides; default warn unless spec requires block
Integration Points
See SKILL.md for complete documentation.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "hook-creatorInput",
"description": "Input schema for 'Creates and registers hooks for the Claude Code framework. Handles pre/post tool execution, validation, memory, and session hooks. Use when new validation, safety, or automation hooks are needed.'",
"type": "object",
"additionalProperties": true,
"properties": {
"target": {
"type": "string",
"description": "Target file or path for the skill to operate on"
},
"options": {
"type": "object",
"description": "Additional options for skill execution",
"additionalProperties": true
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "hook-creatorOutput",
"type": "object",
"additionalProperties": true,
"properties": {
"ok": {
"type": "boolean"
},
"summary": {
"type": "string"
}
}
}
#!/usr/bin/env node
'use strict';
const fs = require('node:fs');
const path = require('node:path');
function findProjectRoot() {
let dir = __dirname;
while (dir !== path.dirname(dir)) {
if (fs.existsSync(path.join(dir, '.claude'))) return dir;
dir = path.dirname(dir);
}
return process.cwd();
}
const PROJECT_ROOT = findProjectRoot();
const CLAUDE_DIR = path.join(PROJECT_ROOT, '.claude');
const HOOKS_DIR = path.join(CLAUDE_DIR, 'hooks');
function parseArgs(argv) {
const options = {};
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (!arg.startsWith('--')) continue;
const key = arg.slice(2);
const next = argv[i + 1];
const hasValue = next && !next.startsWith('--');
options[key] = hasValue ? argv[++i] : true;
}
return options;
}
function registerHookInSettings(name, category, type) {
const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
if (!fs.existsSync(settingsPath)) return;
let settings;
try {
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
} catch (_err) {
return;
}
const hookPath = `.claude/hooks/${category}/${name}.cjs`;
if (!settings.hooks) settings.hooks = {};
let targetArray;
if (type.toLowerCase().includes('pre')) {
if (!settings.hooks['pre-tool']) settings.hooks['pre-tool'] = [];
targetArray = settings.hooks['pre-tool'];
} else if (type.toLowerCase().includes('post')) {
if (!settings.hooks['post-tool']) settings.hooks['post-tool'] = [];
targetArray = settings.hooks['post-tool'];
}
if (targetArray && !targetArray.includes(hookPath)) {
targetArray.push(hookPath);
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
}
}
function updateHooksReadme(name, category, description) {
const readmePath = path.join(HOOKS_DIR, 'README.md');
if (!fs.existsSync(readmePath)) return;
const content = fs.readFileSync(readmePath, 'utf8');
if (content.includes(name)) return;
const entry = `\n#### ${name
.split('-')
.map(p => p[0].toUpperCase() + p.slice(1))
.join(' ')} (\`${name}.cjs\`)\n\n${description}\n`;
fs.appendFileSync(readmePath, entry, 'utf8');
}
function createHook(options) {
const name = String(options.name || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-');
if (!name) throw new Error('Missing required --name');
const category = String(options.category || 'safety').trim();
const type = String(options.type || 'PreToolUse').trim();
const description = String(options.description || 'New framework hook').trim();
const hookDir = path.join(HOOKS_DIR, category);
const hookPath = path.join(hookDir, `${name}.cjs`);
if (fs.existsSync(hookPath)) {
return { ok: true, status: 'exists', path: hookPath };
}
const content = `'use strict';
/**
* ${name}
*
* Type: ${type}
* Purpose: ${description}
*/
function validate(context) {
// const { tool, parameters } = context;
return { valid: true, error: '' };
}
function main() {
const { safeParseJSON } = require('../../../lib/utils/safe-json.cjs');
let input;
try {
input = process.argv[2] ? JSON.parse(process.argv[2]) : null;
} catch (e) {
process.exit(0);
}
if (!input) process.exit(0);
const result = validate({
tool: input.tool_name || input.tool,
parameters: input.tool_input || input.input || {}
});
if (!result.valid) {
console.error('BLOCKED: ' + result.error);
process.exit(1);
}
process.exit(0);
}
if (require.main === module) {
main();
}
module.exports = { validate };
`;
if (!fs.existsSync(hookDir)) fs.mkdirSync(hookDir, { recursive: true });
fs.writeFileSync(hookPath, content, 'utf8');
// POST-CREATION INTEGRATION
try {
registerHookInSettings(name, category, type);
updateHooksReadme(name, category, description);
const learningsPath = path.join(CLAUDE_DIR, 'context', 'memory', 'learnings.md');
if (fs.existsSync(learningsPath)) {
fs.appendFileSync(
learningsPath,
`\n- Created new hook: ${name} in ${category} (${new Date().toISOString().split('T')[0]})\n`,
'utf8'
);
}
} catch (err) {
console.error(`Warning: Integration partial: ${err.message}`);
}
return { ok: true, action: 'create', path: hookPath };
}
function main() {
const options = parseArgs(process.argv.slice(2));
if (options.help || Object.keys(options).length === 0) {
console.log(
'Hook Creator CLI\nUsage: --name <name> --category <cat> --type <type> [--description <desc>]'
);
return;
}
const result = createHook(options);
console.log(JSON.stringify(result, null, 2));
}
if (require.main === module) {
try {
main();
} catch (err) {
console.error(err.message);
process.exit(1);
}
}
hook-creator Implementation Template
Goal
- Define target outcome and acceptance criteria.
TDD
1. Red 2. Green 3. Refactor
Verification
- lint
- format
- targeted tests