Skip to content

Commit 6173445

Browse files
committed
chore: update biome configuration and improve code formatting
- Added "includes" property to biome.json to specify file patterns for inclusion. - Cleaned up formatting in init.ts for improved readability and consistency. These changes enhance project configuration and maintainability.
1 parent 8237bd0 commit 6173445

File tree

4 files changed

+302
-6
lines changed

4 files changed

+302
-6
lines changed

.claude/settings.local.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(git push:*)",
5+
"Bash(bun run:*)",
6+
"Bash(bun run lint:*)",
7+
"Bash(git pull:*)",
8+
"Bash(git stash:*)"
9+
],
10+
"deny": []
11+
}
12+
}

biome.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"useIgnoreFile": true
77
},
88
"files": {
9-
"ignoreUnknown": false
9+
"ignoreUnknown": false,
10+
"includes": ["src/**/*", "templates/**/*", "test/**/*"]
1011
},
1112
"formatter": {
1213
"enabled": true,

scripts/session-type-reference.ts

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
#!/usr/bin/env bun
2+
3+
/**
4+
* Claude Code Hook Payload Schemas
5+
*
6+
* This file provides TypeScript interfaces for all Claude Code hook payloads
7+
* based on the official documentation. Use these for type-safe hook development.
8+
*/
9+
10+
// ============================================================================
11+
// Input Payload Schemas
12+
// ============================================================================
13+
14+
/** Common fields present in all hook payloads */
15+
interface BasePayload {
16+
session_id: string
17+
transcript_path: string
18+
}
19+
20+
/** PreToolUse hook input payload */
21+
export interface PreToolUseInput extends BasePayload {
22+
tool_name: string
23+
tool_input: Record<string, any>
24+
}
25+
26+
/** PostToolUse hook input payload */
27+
export interface PostToolUseInput extends BasePayload {
28+
tool_name: string
29+
tool_input: Record<string, any>
30+
tool_response: Record<string, any> & {
31+
success?: boolean
32+
}
33+
}
34+
35+
/** Notification hook input payload */
36+
export interface NotificationInput extends BasePayload {
37+
message: string
38+
title: string
39+
}
40+
41+
/** Stop hook input payload */
42+
export interface StopInput extends BasePayload {
43+
stop_hook_active: boolean
44+
}
45+
46+
/** SubagentStop hook input payload */
47+
export interface SubagentStopInput extends BasePayload {
48+
stop_hook_active: boolean
49+
}
50+
51+
/** Union type for all possible hook input payloads */
52+
export type HookInput = PreToolUseInput | PostToolUseInput | NotificationInput | StopInput | SubagentStopInput
53+
54+
// ============================================================================
55+
// Output Response Schemas
56+
// ============================================================================
57+
58+
/** Common fields available in all hook responses */
59+
interface BaseResponse {
60+
/** Whether Claude should continue after hook execution (default: true) */
61+
continue?: boolean
62+
/** Message shown to user when continue is false */
63+
stopReason?: string
64+
/** Hide stdout from transcript mode (default: false) */
65+
suppressOutput?: boolean
66+
}
67+
68+
/** PreToolUse hook response */
69+
export interface PreToolUseResponse extends BaseResponse {
70+
/** Decision about tool execution */
71+
decision?: 'approve' | 'block'
72+
/** Explanation for decision (shown to Claude if blocking, to user if approving) */
73+
reason?: string
74+
}
75+
76+
/** PostToolUse hook response */
77+
export interface PostToolUseResponse extends BaseResponse {
78+
/** Decision to provide feedback to Claude */
79+
decision?: 'block'
80+
/** Feedback shown to Claude when decision is 'block' */
81+
reason?: string
82+
}
83+
84+
/** Stop/SubagentStop hook response */
85+
export interface StopResponse extends BaseResponse {
86+
/** Decision about whether Claude can stop */
87+
decision?: 'block'
88+
/** REQUIRED when decision is 'block' - tells Claude how to proceed */
89+
reason?: string
90+
}
91+
92+
/** Notification hook response (uses base response only) */
93+
export type NotificationResponse = BaseResponse
94+
95+
/** SubagentStop response (same as Stop) */
96+
export type SubagentStopResponse = StopResponse
97+
98+
// ============================================================================
99+
// Tool-Specific Input Schemas
100+
// ============================================================================
101+
102+
/** Common tool input types */
103+
export namespace ToolInputs {
104+
export interface Write {
105+
file_path: string
106+
content: string
107+
}
108+
109+
export interface Edit {
110+
file_path: string
111+
old_string: string
112+
new_string: string
113+
replace_all?: boolean
114+
}
115+
116+
export interface Bash {
117+
command: string
118+
timeout?: number
119+
description?: string
120+
}
121+
122+
export interface Read {
123+
file_path: string
124+
limit?: number
125+
offset?: number
126+
}
127+
128+
export interface Glob {
129+
pattern: string
130+
path?: string
131+
}
132+
133+
export interface Grep {
134+
pattern: string
135+
path?: string
136+
include?: string
137+
}
138+
}
139+
140+
// ============================================================================
141+
// Type Guards
142+
// ============================================================================
143+
144+
/** Type guard to check if input is PreToolUse */
145+
export function isPreToolUseInput(input: HookInput): input is PreToolUseInput {
146+
return 'tool_name' in input && !('tool_response' in input)
147+
}
148+
149+
/** Type guard to check if input is PostToolUse */
150+
export function isPostToolUseInput(input: HookInput): input is PostToolUseInput {
151+
return 'tool_name' in input && 'tool_response' in input
152+
}
153+
154+
/** Type guard to check if input is Notification */
155+
export function isNotificationInput(input: HookInput): input is NotificationInput {
156+
return 'message' in input && 'title' in input
157+
}
158+
159+
/** Type guard to check if input is Stop or SubagentStop */
160+
export function isStopInput(input: HookInput): input is StopInput | SubagentStopInput {
161+
return 'stop_hook_active' in input
162+
}
163+
164+
// ============================================================================
165+
// Validation Functions
166+
// ============================================================================
167+
168+
/** Validates that a response has required fields when blocking */
169+
export function validateStopResponse(response: StopResponse): string | null {
170+
if (response.decision === 'block' && !response.reason) {
171+
return 'reason is required when decision is "block"'
172+
}
173+
return null
174+
}
175+
176+
/** Validates hook response based on hook type */
177+
export function validateHookResponse(
178+
hookType: 'PreToolUse' | 'PostToolUse' | 'Stop' | 'SubagentStop' | 'Notification',
179+
response: any,
180+
): string | null {
181+
// Check for valid decision values based on hook type
182+
if ('decision' in response) {
183+
switch (hookType) {
184+
case 'PreToolUse':
185+
if (response.decision && !['approve', 'block'].includes(response.decision)) {
186+
return `Invalid decision for PreToolUse: ${response.decision}`
187+
}
188+
break
189+
case 'PostToolUse':
190+
case 'Stop':
191+
case 'SubagentStop':
192+
if (response.decision && response.decision !== 'block') {
193+
return `Invalid decision for ${hookType}: ${response.decision}`
194+
}
195+
if (hookType === 'Stop' || hookType === 'SubagentStop') {
196+
const error = validateStopResponse(response)
197+
if (error) return error
198+
}
199+
break
200+
case 'Notification':
201+
if (response.decision) {
202+
return 'Notification hooks should not have a decision field'
203+
}
204+
break
205+
}
206+
}
207+
208+
return null
209+
}
210+
211+
// ============================================================================
212+
// Example Usage
213+
// ============================================================================
214+
215+
/** Example minimal payloads for testing */
216+
export const examplePayloads = {
217+
preToolUse: {
218+
session_id: 'abc123',
219+
transcript_path: '/path/to/transcript.jsonl',
220+
tool_name: 'Write',
221+
tool_input: {
222+
file_path: '/path/to/file.txt',
223+
content: 'Hello, world!',
224+
},
225+
} as PreToolUseInput,
226+
227+
postToolUse: {
228+
session_id: 'abc123',
229+
transcript_path: '/path/to/transcript.jsonl',
230+
tool_name: 'Write',
231+
tool_input: {
232+
file_path: '/path/to/file.txt',
233+
content: 'Hello, world!',
234+
},
235+
tool_response: {
236+
success: true,
237+
filePath: '/path/to/file.txt',
238+
},
239+
} as PostToolUseInput,
240+
241+
notification: {
242+
session_id: 'abc123',
243+
transcript_path: '/path/to/transcript.jsonl',
244+
message: 'Task completed successfully',
245+
title: 'Claude Code',
246+
} as NotificationInput,
247+
248+
stop: {
249+
session_id: 'abc123',
250+
transcript_path: '/path/to/transcript.jsonl',
251+
stop_hook_active: false,
252+
} as StopInput,
253+
}
254+
255+
/** Example responses */
256+
export const exampleResponses = {
257+
// Approve a tool call
258+
approveToolCall: {
259+
decision: 'approve',
260+
reason: 'Approved by security policy',
261+
} as PreToolUseResponse,
262+
263+
// Block a dangerous command
264+
blockDangerousCommand: {
265+
decision: 'block',
266+
reason: 'Command contains dangerous operation: rm -rf /',
267+
} as PreToolUseResponse,
268+
269+
// Continue with default behavior
270+
continueDefault: {} as BaseResponse,
271+
272+
// Stop all processing
273+
stopProcessing: {
274+
continue: false,
275+
stopReason: 'Critical error detected',
276+
} as BaseResponse,
277+
278+
// Force Claude to continue (Stop hook)
279+
forceContinue: {
280+
decision: 'block',
281+
reason: 'Please also update the documentation for this change',
282+
} as StopResponse,
283+
}

src/commands/init.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as path from 'node:path'
2-
import { fileURLToPath } from 'node:url'
3-
import { Command, Flags } from '@oclif/core'
2+
import {fileURLToPath} from 'node:url'
3+
import {Command, Flags} from '@oclif/core'
44
import chalk from 'chalk'
55
import fs from 'fs-extra'
66
import ora from 'ora'
@@ -34,16 +34,16 @@ This command sets up basic Claude Code hooks in your project:
3434
}
3535

3636
public async run(): Promise<void> {
37-
const { flags } = await this.parse(Init)
37+
const {flags} = await this.parse(Init)
3838

3939
console.log(chalk.blue.bold('\n🪝 Claude Hooks Setup\n'))
4040

4141
// Check if Bun is installed
42-
const { spawn } = await import('node:child_process')
42+
const {spawn} = await import('node:child_process')
4343
const isWindows = process.platform === 'win32'
4444
const command = isWindows ? 'where' : 'which'
4545
const checkBun = await new Promise<boolean>((resolve) => {
46-
const child = spawn(command, ['bun'], { shell: false })
46+
const child = spawn(command, ['bun'], {shell: false})
4747
child.on('error', () => resolve(false))
4848
child.on('exit', (code) => resolve(code === 0))
4949
})

0 commit comments

Comments
 (0)