Files
gh-epieczko-betty/skills/hook.simulate/SKILL.md
2025-11-29 18:26:08 +08:00

558 lines
14 KiB
Markdown

# hook.simulate
**Version:** 0.1.0
**Status:** Active
**Tags:** hook, simulation, testing, validation, development
## Overview
The `hook.simulate` skill allows developers to test a hook manifest before registering it in the Betty Framework. It validates the manifest structure, simulates hook triggers based on event types, and optionally executes the hook commands in a controlled environment.
This skill is essential for:
- **Testing hooks before deployment** - Catch errors early in development
- **Understanding hook behavior** - See which files match patterns and how commands execute
- **Debugging hook issues** - Validate patterns, commands, and execution flow
- **Safe experimentation** - Test hooks without affecting the live system
## Features
-**Manifest Validation** - Validates all required and optional fields
-**Pattern Matching** - Shows which files match the hook's pattern
-**Event Simulation** - Simulates different hook events (on_file_edit, on_commit, etc.)
-**Command Execution** - Runs hook commands with dry-run or actual execution
-**Timeout Testing** - Validates timeout behavior
-**Blocking Simulation** - Shows how blocking hooks would behave
-**JSON Output** - Structured results for automation and analysis
## Usage
### Command Line
```bash
# Basic validation and simulation
python skills/hook.simulate/hook_simulate.py examples/test-hook.yaml
# Simulate with dry-run command execution
python skills/hook.simulate/hook_simulate.py examples/test-hook.yaml --execute --dry-run
# Actually execute the command
python skills/hook.simulate/hook_simulate.py examples/test-hook.yaml --execute --no-dry-run
# Get JSON output for scripting
python skills/hook.simulate/hook_simulate.py examples/test-hook.yaml --output json
```
### As a Skill
```python
from skills.hook.simulate.hook_simulate import simulate_hook
# Simulate a hook
results = simulate_hook(
manifest_path="examples/test-hook.yaml",
dry_run=True,
execute=True
)
# Check validation
if results["valid"]:
print("✅ Hook is valid")
else:
print("❌ Validation errors:", results["validation_errors"])
# Check if hook would trigger
if results["trigger_simulation"]["would_trigger"]:
print("Hook would trigger!")
print("Matching files:", results["trigger_simulation"]["matching_files"])
```
## Input Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `manifest_path` | string | Yes | - | Path to the hook.yaml manifest file |
| `execute` | boolean | No | false | Whether to execute the hook command |
| `dry_run` | boolean | No | true | If true, simulate without actually running commands |
## Output Schema
```json
{
"manifest_path": "path/to/hook.yaml",
"timestamp": "2025-10-23 12:34:56",
"valid": true,
"validation_errors": [],
"manifest": {
"name": "hook-name",
"version": "0.1.0",
"description": "Hook description",
"event": "on_file_edit",
"command": "python script.py {file_path}",
"when": {
"pattern": "*.yaml"
},
"blocking": true,
"timeout": 30000
},
"trigger_simulation": {
"would_trigger": true,
"reason": "Found 5 file(s) matching pattern: *.yaml",
"matching_files": ["file1.yaml", "file2.yaml"],
"pattern": "*.yaml"
},
"command_executions": [
{
"command": "python script.py file1.yaml",
"executed": true,
"dry_run": false,
"stdout": "Validation passed",
"stderr": "",
"return_code": 0,
"execution_time_ms": 123.45,
"success": true,
"file": "file1.yaml"
}
],
"blocking": true,
"timeout_ms": 30000
}
```
## Examples
### Example 1: Validate OpenAPI Hook
Create a hook manifest `openapi-validator.yaml`:
```yaml
name: validate-openapi
version: 0.1.0
description: "Validate OpenAPI specifications on edit"
event: on_file_edit
command: "python betty/skills/api.validate/api_validate.py {file_path} zalando"
when:
pattern: "**/*.openapi.yaml"
blocking: true
timeout: 30000
on_failure: show_errors
status: draft
tags: [api, validation, openapi]
```
Simulate it:
```bash
python skills/hook.simulate/hook_simulate.py openapi-validator.yaml
```
Output:
```
=== Hook Simulation Results ===
Manifest: openapi-validator.yaml
Timestamp: 2025-10-23 12:34:56
✅ VALIDATION PASSED
Hook: validate-openapi v0.1.0
Event: on_file_edit
Command: python betty/skills/api.validate/api_validate.py {file_path} zalando
Blocking: True
Timeout: 30000ms
✅ WOULD TRIGGER
Reason: Found 3 file(s) matching pattern: **/*.openapi.yaml
Matching files (3):
- specs/petstore.openapi.yaml
- specs/users.openapi.yaml
- api/products.openapi.yaml
```
### Example 2: Test Commit Hook
Create `pre-commit.yaml`:
```yaml
name: pre-commit-linter
version: 1.0.0
description: "Run linter before commits"
event: on_commit
command: "pylint src/"
blocking: true
timeout: 60000
status: active
tags: [lint, quality]
```
Simulate with execution:
```bash
python skills/hook.simulate/hook_simulate.py pre-commit.yaml --execute --dry-run
```
Output:
```
=== Hook Simulation Results ===
Manifest: pre-commit.yaml
Timestamp: 2025-10-23 12:35:00
✅ VALIDATION PASSED
Hook: pre-commit-linter v1.0.0
Event: on_commit
Command: pylint src/
Blocking: True
Timeout: 60000ms
✅ WOULD TRIGGER
Reason: on_commit hook would trigger with 5 changed file(s)
Changed files (5):
- src/main.py
- src/utils.py
- tests/test_main.py
- README.md
- setup.py
=== Command Execution Results (1) ===
[1] N/A
Command: pylint src/
Mode: DRY RUN (not executed)
```
### Example 3: Test with Actual Execution
```bash
python skills/hook.simulate/hook_simulate.py openapi-validator.yaml --execute --no-dry-run
```
Output:
```
=== Hook Simulation Results ===
...
=== Command Execution Results (3) ===
[1] specs/petstore.openapi.yaml
Command: python betty/skills/api.validate/api_validate.py specs/petstore.openapi.yaml zalando
Executed: Yes
Return code: 0
Execution time: 234.56ms
Status: ✅ SUCCESS
Stdout:
✅ OpenAPI spec is valid
[2] specs/users.openapi.yaml
Command: python betty/skills/api.validate/api_validate.py specs/users.openapi.yaml zalando
Executed: Yes
Return code: 1
Execution time: 189.23ms
Status: ❌ FAILED
Stderr:
Error: Missing required field 'info.contact'
[3] api/products.openapi.yaml
Command: python betty/skills/api.validate/api_validate.py api/products.openapi.yaml zalando
Executed: Yes
Return code: 0
Execution time: 201.45ms
Status: ✅ SUCCESS
```
### Example 4: JSON Output for Automation
```bash
python skills/hook.simulate/hook_simulate.py my-hook.yaml --output json > results.json
```
Then process with scripts:
```python
import json
with open("results.json") as f:
results = json.load(f)
if not results["valid"]:
print("Validation failed:")
for error in results["validation_errors"]:
print(f" - {error}")
exit(1)
if results["trigger_simulation"]["would_trigger"]:
files = results["trigger_simulation"]["matching_files"]
print(f"Hook would run on {len(files)} files")
for execution in results["command_executions"]:
if not execution["success"]:
print(f"Failed on {execution['file']}")
print(f"Error: {execution['stderr']}")
```
## Event Type Support
| Event | Simulation Support | Description |
|-------|-------------------|-------------|
| `on_file_edit` | ✅ Full | Matches files by pattern, simulates editing |
| `on_file_save` | ✅ Full | Similar to on_file_edit |
| `on_commit` | ✅ Full | Checks git status, shows changed files |
| `on_push` | ⚠️ Partial | Notes hook would trigger, no git simulation |
| `on_tool_use` | ⚠️ Partial | Notes hook would trigger, no tool simulation |
| `on_agent_start` | ⚠️ Partial | Notes hook would trigger, no agent simulation |
| `on_workflow_end` | ⚠️ Partial | Notes hook would trigger, no workflow simulation |
## Validation Rules
The skill validates all Betty Framework hook requirements:
1. **Required Fields**: name, version, description, event, command
2. **Name Format**: Lowercase, starts with letter, allows hyphens/underscores
3. **Version Format**: Semantic versioning (e.g., 1.0.0, 0.1.0-alpha)
4. **Event**: Must be a valid hook event
5. **Command**: Non-empty string
6. **Blocking**: Must be boolean if specified
7. **Timeout**: Must be positive number in milliseconds if specified
8. **Status**: Must be draft, active, disabled, or archived if specified
9. **Pattern**: Must be non-empty string if specified in when.pattern
## Pattern Matching
The skill supports glob patterns for file matching:
| Pattern | Description | Example Matches |
|---------|-------------|-----------------|
| `*.yaml` | Files in current directory | `config.yaml`, `spec.yaml` |
| `**/*.yaml` | YAML files anywhere | `src/config.yaml`, `specs/api/v1.yaml` |
| `src/**/*.py` | Python files in src/ | `src/main.py`, `src/utils/helper.py` |
| `*.openapi.yaml` | OpenAPI files only | `petstore.openapi.yaml` |
| `tests/**/*.test.js` | Test files | `tests/unit/api.test.js` |
## Dry-Run vs Execution Modes
### Dry-Run Mode (Default)
```bash
python skills/hook.simulate/hook_simulate.py hook.yaml --execute --dry-run
```
- ✅ Validates manifest
- ✅ Shows matching files
- ✅ Shows command that would run
- ❌ Does NOT execute command
- ⚡ Safe for testing
### Execution Mode
```bash
python skills/hook.simulate/hook_simulate.py hook.yaml --execute --no-dry-run
```
- ✅ Validates manifest
- ✅ Shows matching files
- ✅ Actually executes command
- ✅ Captures stdout/stderr
- ✅ Reports execution time
- ⚠️ Use with caution
### Validation Only (No Execution)
```bash
python skills/hook.simulate/hook_simulate.py hook.yaml
```
- ✅ Validates manifest
- ✅ Shows if hook would trigger
- ✅ Shows matching files
- ❌ Does NOT execute command
- ⚡ Fastest mode
## Integration with Hook Workflow
### Development Workflow
```bash
# 1. Create hook manifest
cat > my-hook.yaml <<EOF
name: my-custom-hook
version: 0.1.0
description: "My hook description"
event: on_file_edit
command: "echo 'Hook triggered on {file_path}'"
when:
pattern: "*.md"
EOF
# 2. Simulate and validate
python skills/hook.simulate/hook_simulate.py my-hook.yaml
# 3. Test with execution
python skills/hook.simulate/hook_simulate.py my-hook.yaml --execute --dry-run
# 4. Test actual execution
python skills/hook.simulate/hook_simulate.py my-hook.yaml --execute --no-dry-run
# 5. Register if tests pass
python skills/hook.register/hook_register.py my-hook.yaml
```
### CI/CD Integration
```yaml
# .github/workflows/test-hooks.yml
name: Test Hooks
on: [push, pull_request]
jobs:
test-hooks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Validate all hook manifests
run: |
for hook in hooks/*.yaml; do
echo "Testing $hook"
python skills/hook.simulate/hook_simulate.py "$hook" --output json || exit 1
done
- name: Test hook execution
run: |
python skills/hook.simulate/hook_simulate.py hooks/validator.yaml --execute --no-dry-run
```
## Error Handling
### Validation Errors
```bash
$ python skills/hook.simulate/hook_simulate.py invalid-hook.yaml
=== Hook Simulation Results ===
❌ VALIDATION FAILED
Errors:
- Missing required field: 'version'
- Invalid event: 'on_invalid_event'. Must be one of: on_file_edit, on_file_save, on_commit, on_push, on_tool_use, on_agent_start, on_workflow_end
- timeout must be a positive number (in milliseconds)
```
### Execution Failures
```bash
$ python skills/hook.simulate/hook_simulate.py hook.yaml --execute --no-dry-run
...
=== Command Execution Results (1) ===
[1] test.yaml
Command: python nonexistent.py test.yaml
Executed: Yes
Return code: -1
Execution time: 45.23ms
Status: ❌ FAILED
Stderr:
Error executing command: [Errno 2] No such file or directory: 'python nonexistent.py test.yaml'
```
### Timeout Handling
```bash
$ python skills/hook.simulate/hook_simulate.py slow-hook.yaml --execute --no-dry-run
...
[1] test.yaml
Command: sleep 100
Executed: Yes
Return code: -1
Execution time: 5000.12ms
Status: ❌ FAILED
Stderr:
Command timed out after 5000ms
```
## Troubleshooting
### Hook Doesn't Trigger
**Problem:** `would_trigger: false`
**Solutions:**
1. Check pattern matches files: `ls -la **/*.yaml`
2. Verify event type is appropriate
3. Ensure files exist in the manifest's directory
4. Check git status for on_commit events
### Command Fails
**Problem:** Command returns non-zero exit code
**Solutions:**
1. Test command manually: `python script.py file.yaml`
2. Check file paths and placeholders
3. Verify dependencies are installed
4. Check timeout is sufficient
### Pattern Not Matching
**Problem:** Expected files not showing in matching_files
**Solutions:**
1. Use `**/*.ext` for recursive matching
2. Check you're in the right directory
3. Test pattern with: `ls **/*.yaml`
4. Ensure pattern is in `when.pattern` field
## Best Practices
1. **Always simulate before registering** - Catch issues early
2. **Test with dry-run first** - Understand what will execute
3. **Verify pattern matching** - Ensure correct files are targeted
4. **Test timeout values** - Make sure commands complete in time
5. **Use JSON output for automation** - Integrate with CI/CD
6. **Test blocking behavior** - Understand impact on workflow
7. **Validate on representative data** - Test with real files
## Related Skills
- **hook.define** - Create hooks dynamically and add to `.claude/hooks.yaml`
- **hook.register** - Register validated hooks in the registry
- **api.validate** - Example validation skill often used in hooks
## Version History
- **0.1.0** (2025-10-23) - Initial release
- Manifest validation
- Event simulation (on_file_edit, on_commit)
- Pattern matching
- Command execution (dry-run and actual)
- JSON and summary output formats
## License
MIT License - Part of the Betty Framework
## Contributing
To improve this skill:
1. Add support for more event types
2. Enhance simulation accuracy
3. Add more validation rules
4. Improve error messages
5. Add performance benchmarks