Skip to content

Commit 0b9ce71

Browse files
authored
Merge pull request #324 from neongreen/copilot/add-global-install-flag
aihook: Add prevent-stop hook and --install flag
2 parents 189f053 + bddc776 commit 0b9ce71

6 files changed

Lines changed: 677 additions & 59 deletions

File tree

aihook/AGENTS.md

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,34 @@
22

33
## Project Overview
44

5-
`aihook` is a validator for Claude Code hooks that enforces shell scripting best practices. The primary use case is validating that `cd` commands are always executed within subshells.
5+
`aihook` is a toolkit for Claude Code hooks that provides validators and behavior modifiers. It includes:
6+
- Shell validation hook to enforce `cd` commands are always executed within subshells
7+
- Prevent-stop hook to prevent Claude from stopping prematurely
8+
- Global `--install` flag to add hooks to `~/.claude/settings.json`
69

710
## Project Structure
811

912
```
1013
aihook/
1114
├── main.go # CLI entry point (Cobra setup)
1215
├── pkg/
16+
│ ├── installer/
17+
│ │ ├── installer.go # Hook installation to ~/.claude/settings.json
18+
│ │ └── installer_test.go # Installer tests
1319
│ └── validator/
1420
│ ├── validator.go # Core validation logic
1521
│ └── validator_test.go # Comprehensive unit tests
22+
├── hook_response_test.go # Tests for hook response functions
23+
├── integration_test.go # Integration tests
1624
├── README.md # User documentation
1725
└── AGENTS.md # This file
1826
```
1927

2028
**Separation of concerns:**
21-
- `main.go`: Cobra CLI setup, flag handling, output formatting
29+
- `main.go`: Cobra CLI setup, flag handling, output formatting, hook commands
2230
- `pkg/validator`: Pure validation logic (AST walking, cd detection)
23-
- Tests are in the validator package, testing the pure logic
31+
- `pkg/installer`: Installation logic for adding hooks to Claude settings
32+
- Tests are in each package, testing the pure logic
2433

2534
## Development Guidelines
2635

@@ -33,35 +42,50 @@ Always run tests and build from the repository root:
3342
go test ./aihook/...
3443

3544
# Build
36-
go build ./aihook
45+
go build -o /tmp/aihook ./aihook
3746

3847
# Run
39-
./aihook shell < script.sh
48+
/tmp/aihook shell < script.sh
49+
/tmp/aihook prevent-stop --install
4050
```
4151

4252
### Code Structure
4353

44-
- `main.go` (86 lines) - CLI entry point with Cobra framework
45-
- Command definitions and flag handling
54+
- `main.go` - CLI entry point with Cobra framework
55+
- Command definitions (shell, prevent-stop)
56+
- Global `--install` flag
4657
- Output formatting (regular and --claude JSON)
47-
- Calls into validator package for logic
58+
- Hook response functions
4859

49-
- `pkg/validator/validator.go` (106 lines) - Core validation logic
60+
- `pkg/validator/validator.go` - Core validation logic
5061
- `Validator` type with `ValidateScript()` method
5162
- AST walking to detect cd commands
5263
- Tracks subshell context (both `(...)` and `$(...)`)
5364
- `FormatViolations()` for user-friendly error messages
5465

55-
- `pkg/validator/validator_test.go` (330 lines) - Comprehensive tests
56-
- 41 test scenarios covering all edge cases
57-
- Tests the validator package directly
66+
- `pkg/installer/installer.go` - Hook installation
67+
- `InstallHook()` function to add hooks to `~/.claude/settings.json`
68+
- Handles creating the settings file if it doesn't exist
69+
- Prevents duplicate hook installation
70+
71+
### Available Commands
72+
73+
1. **`shell`**: Validates Bash commands to ensure `cd` is only used in subshells
74+
- `--claude`: Output in JSON format
75+
- `--block-on-cd`: Deny execution when violations found
76+
77+
2. **`prevent-stop`**: Prevents Claude from stopping prematurely
78+
- Returns `continue: false` with a message telling Claude to keep working
79+
80+
3. **`--install` (global flag)**: Installs the hook to `~/.claude/settings.json`
5881

5982
### Key Implementation Details
6083

6184
1. **Shell Parsing**: Uses `mvdan.cc/sh/v3/syntax` for accurate shell script parsing
6285
2. **AST Walking**: Traverses the syntax tree to find `cd` commands and track subshell context
6386
3. **Subshell Detection**: Handles both explicit subshells `(...)` and command substitutions `$(...)`
6487
4. **Output Formats**: Supports both human-readable and Claude Code JSON formats
88+
5. **Hook Installation**: Modifies `~/.claude/settings.json` to add hooks
6589

6690
### Testing Philosophy
6791

@@ -72,6 +96,9 @@ Tests cover:
7296
- Edge cases (loops, conditionals, functions)
7397
- Invalid shell syntax
7498
- Multiple cd commands in one script
99+
- Hook installation to settings file
100+
- Duplicate installation prevention
101+
- Hook response JSON structure
75102

76103
All tests must pass before any changes are committed.
77104

@@ -81,29 +108,39 @@ All tests must pass before any changes are committed.
81108
2. **Command Substitution**: `$(...)` IS a subshell and cd is allowed inside
82109
3. **Here Documents**: Content inside here-docs should not be parsed as commands
83110

84-
### Claude Code Hook Format
111+
### Claude Code Hook Response Format
85112

86-
When `--claude` flag is used, output must be JSON with:
87-
- `exit_code`: Integer (0 for success, 2 for violations, 1 for errors)
88-
- `message`: String containing the human-readable message
113+
For PreToolUse hooks:
114+
```json
115+
{
116+
"continue": true,
117+
"hookSpecificOutput": {
118+
"hookEventName": "PreToolUse",
119+
"permissionDecision": "allow|deny",
120+
"permissionDecisionReason": "..."
121+
}
122+
}
123+
```
89124

90-
Example:
125+
For Stop hooks (prevent-stop):
91126
```json
92127
{
93-
"exit_code": 2,
94-
"message": "Found cd commands outside subshells:\n Line 1: 'cd' command found outside subshell\n\nAll 'cd' commands must be in a subshell. Example:\n # Bad: cd /tmp && ls\n # Good: (cd /tmp && ls)\n"
128+
"continue": false,
129+
"stopReason": "Keep working!",
130+
"systemMessage": "Continue working on the task..."
95131
}
96132
```
97133

98134
## Adding New Hook Types
99135

100-
When adding new hook types in the future:
136+
When adding new hook types:
101137

102138
1. Add a new subcommand in `main.go`
103139
2. Create a new validator in `pkg/validator` if needed
104-
3. Add comprehensive unit tests in the validator package
105-
4. Update README.md with usage examples
106-
5. Update this AGENTS.md with implementation notes
140+
3. Add support in `pkg/installer` for the new event type
141+
4. Add comprehensive unit tests
142+
5. Update README.md with usage examples
143+
6. Update this AGENTS.md with implementation notes
107144

108145
## Dependencies
109146

aihook/README.md

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
# aihook
22

3-
A validator for Claude Code hooks that enforces shell scripting best practices.
3+
A toolkit for Claude Code hooks including validators and behavior modifiers.
44

55
## Overview
66

7-
`aihook` is a command-line tool designed to validate shell scripts for use in Claude Code PreToolUse hooks. It parses shell syntax and enforces specific rules to ensure code quality and consistency.
7+
`aihook` is a command-line tool that provides various hooks for Claude Code. It can validate shell scripts and modify Claude's behavior (like preventing premature stopping).
88

99
## Features
1010

11-
- **PreToolUse Hook**: Validates Bash commands before execution to forbid `cd` invocations outside subshells
11+
- **Shell Validation Hook**: Validates Bash commands before execution to forbid `cd` invocations outside subshells
12+
- **Prevent-Stop Hook**: Prevents Claude from stopping prematurely, forcing it to continue working
13+
- **Global Install**: Use `--install` flag to add hooks to `~/.claude/settings.json` automatically
1214
- **Shell Parser**: Uses `mvdan.cc/sh/v3/syntax` for accurate shell script parsing
13-
- **Claude Code Integration**: Supports `--claude` flag for JSON output compatible with Claude Code hooks
14-
- **Comprehensive Validation**: Handles complex scenarios including:
15-
- Nested subshells
16-
- Command substitution (`$(...)`)
17-
- Conditional statements
18-
- Loops and functions
19-
- Here documents
15+
- **Claude Code Integration**: Supports JSON output compatible with Claude Code hooks
2016

2117
## Installation
2218

@@ -29,11 +25,47 @@ go install github.com/neongreen/mono/aihook@latest
2925
### Local Development
3026

3127
```bash
32-
go build ./aihook
28+
go build -o /tmp/aihook ./aihook
3329
```
3430

3531
## Usage
3632

33+
### Global --install Flag
34+
35+
All hooks support the `--install` flag which adds the hook to your global Claude settings (`~/.claude/settings.json`) instead of running it:
36+
37+
```bash
38+
# Install the prevent-stop hook
39+
aihook prevent-stop --install
40+
41+
# Install the shell hook
42+
aihook shell --install
43+
44+
# Install shell hook with block-on-cd behavior
45+
aihook shell --install --block-on-cd
46+
```
47+
48+
### Prevent-Stop Hook
49+
50+
The `prevent-stop` subcommand prevents Claude from stopping prematurely:
51+
52+
```bash
53+
# Run as a hook (reads JSON input from stdin)
54+
echo '{}' | aihook prevent-stop
55+
56+
# Install to global Claude settings
57+
aihook prevent-stop --install
58+
```
59+
60+
Output when running:
61+
```json
62+
{
63+
"continue": false,
64+
"stopReason": "Keep working! Don't stop until the task is fully complete.",
65+
"systemMessage": "You are not done yet. Continue working on the task until it is fully complete. Do not stop prematurely."
66+
}
67+
```
68+
3769
### Shell Hook
3870

3971
The `shell` subcommand validates shell scripts and ensures all `cd` commands are executed within subshells:
@@ -47,6 +79,9 @@ echo 'cd /tmp' | aihook shell --claude
4779

4880
# Block execution when cd violations are found
4981
echo 'cd /tmp' | aihook shell --claude --block-on-cd
82+
83+
# Install to global Claude settings
84+
aihook shell --install --block-on-cd
5085
```
5186

5287
### Examples
@@ -63,6 +98,10 @@ cd /tmp && ls
6398

6499
### Flags
65100

101+
Global flags:
102+
- `--install`: Install hook to global Claude settings (`~/.claude/settings.json`) instead of running it
103+
104+
Shell hook flags:
66105
- `--claude`: Output in JSON format compatible with Claude Code hooks
67106
- `--block-on-cd`: When set, returns exit code 2 for violations (blocks execution). Without this flag, violations are reported but execution is not blocked (exit code 0)
68107

@@ -87,14 +126,20 @@ All 'cd' commands must be in a subshell. Example:
87126
#### Claude Code Hook Format (`--claude`)
88127
```json
89128
{
90-
"exit_code": 2,
91-
"message": "Found cd commands outside subshells:\n Line 1: 'cd' command found outside subshell\n\nAll 'cd' commands must be in a subshell. Example:\n # Bad: cd /tmp && ls\n # Good: (cd /tmp && ls)\n"
129+
"continue": true,
130+
"hookSpecificOutput": {
131+
"hookEventName": "PreToolUse",
132+
"permissionDecision": "deny",
133+
"permissionDecisionReason": "Found cd commands outside subshells..."
134+
}
92135
}
93136
```
94137

95138
## Claude Code Integration
96139

97-
To use `aihook` as a Claude Code PreToolUse hook that validates Bash commands, add this to your `.claude/settings.json`:
140+
### Manual Configuration
141+
142+
To use `aihook` as a Claude Code hook, add this to your `.claude/settings.json`:
98143

99144
```json
100145
{
@@ -109,20 +154,35 @@ To use `aihook` as a Claude Code PreToolUse hook that validates Bash commands, a
109154
}
110155
]
111156
}
157+
],
158+
"Stop": [
159+
{
160+
"matcher": "stop",
161+
"hooks": [
162+
{
163+
"type": "command",
164+
"command": "aihook prevent-stop"
165+
}
166+
]
167+
}
112168
]
113169
}
114170
}
115171
```
116172

117-
The hook will:
118-
- Match any Bash tool invocation (via the `"Bash"` matcher)
119-
- Receive the bash command script on stdin
120-
- Validate that all `cd` commands are in subshells
121-
- With `--block-on-cd`: Return exit code 2 to block execution when violations are found
122-
- Without `--block-on-cd`: Report violations but allow execution (exit code 0)
123-
- Use `--claude` flag to output in the expected JSON format
173+
### Automatic Installation
174+
175+
Use the `--install` flag to automatically add hooks to your global Claude settings:
176+
177+
```bash
178+
# Install prevent-stop hook
179+
aihook prevent-stop --install
180+
181+
# Install shell hook with cd blocking
182+
aihook shell --install --block-on-cd
183+
```
124184

125-
For more flexible matching, you can use regex patterns like `"Bash.*cd"` to only check Bash commands containing `cd`.
185+
The hooks will be added to `~/.claude/settings.json`, creating the file if it doesn't exist.
126186

127187
## Why Forbid cd Outside Subshells?
128188

@@ -143,13 +203,13 @@ By requiring `cd` in subshells `(cd /path && command)`, you ensure:
143203
### Running Tests
144204

145205
```bash
146-
go test ./aihook -v
206+
go test ./aihook/... -v
147207
```
148208

149209
### Building
150210

151211
```bash
152-
go build ./aihook
212+
go build -o /tmp/aihook ./aihook
153213
```
154214

155215
## License

0 commit comments

Comments
 (0)