@hasna/hooks 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmrc.example +2 -0
- package/AGENTS.md +54 -0
- package/CLAUDE.md +70 -0
- package/CONTRIBUTING.md +45 -0
- package/README.md +232 -0
- package/bin/index.js +5171 -0
- package/hooks/hook-agentmessages/CLAUDE.md +79 -0
- package/hooks/hook-agentmessages/LICENSE +21 -0
- package/hooks/hook-agentmessages/README.md +107 -0
- package/hooks/hook-agentmessages/package.json +31 -0
- package/hooks/hook-agentmessages/src/check-messages.ts +151 -0
- package/hooks/hook-agentmessages/src/install.ts +126 -0
- package/hooks/hook-agentmessages/src/session-start.ts +255 -0
- package/hooks/hook-agentmessages/src/uninstall.ts +89 -0
- package/hooks/hook-branchprotect/CLAUDE.md +23 -0
- package/hooks/hook-branchprotect/README.md +25 -0
- package/hooks/hook-branchprotect/package.json +42 -0
- package/hooks/hook-branchprotect/src/cli.ts +126 -0
- package/hooks/hook-branchprotect/src/hook.ts +88 -0
- package/hooks/hook-branchprotect/tsconfig.json +25 -0
- package/hooks/hook-checkbugs/LICENSE +21 -0
- package/hooks/hook-checkbugs/README.md +140 -0
- package/hooks/hook-checkbugs/package.json +58 -0
- package/hooks/hook-checkbugs/src/cli.ts +628 -0
- package/hooks/hook-checkbugs/src/hook.ts +335 -0
- package/hooks/hook-checkbugs/tsconfig.json +15 -0
- package/hooks/hook-checkdocs/README.md +137 -0
- package/hooks/hook-checkdocs/package.json +57 -0
- package/hooks/hook-checkdocs/src/cli.ts +628 -0
- package/hooks/hook-checkdocs/src/hook.ts +310 -0
- package/hooks/hook-checkdocs/tsconfig.json +15 -0
- package/hooks/hook-checkfiles/LICENSE +21 -0
- package/hooks/hook-checkfiles/README.md +141 -0
- package/hooks/hook-checkfiles/package.json +56 -0
- package/hooks/hook-checkfiles/src/cli.ts +545 -0
- package/hooks/hook-checkfiles/src/hook.ts +321 -0
- package/hooks/hook-checkfiles/tsconfig.json +15 -0
- package/hooks/hook-checklint/LICENSE +21 -0
- package/hooks/hook-checklint/README.md +147 -0
- package/hooks/hook-checklint/package.json +57 -0
- package/hooks/hook-checklint/src/cli-patch.ts +32 -0
- package/hooks/hook-checklint/src/cli.ts +667 -0
- package/hooks/hook-checklint/src/hook.ts +473 -0
- package/hooks/hook-checklint/tsconfig.json +15 -0
- package/hooks/hook-checkpoint/CLAUDE.md +23 -0
- package/hooks/hook-checkpoint/README.md +37 -0
- package/hooks/hook-checkpoint/package.json +58 -0
- package/hooks/hook-checkpoint/src/cli.ts +191 -0
- package/hooks/hook-checkpoint/src/hook.ts +207 -0
- package/hooks/hook-checkpoint/tsconfig.json +25 -0
- package/hooks/hook-checksecurity/LICENSE +21 -0
- package/hooks/hook-checksecurity/README.md +158 -0
- package/hooks/hook-checksecurity/package.json +57 -0
- package/hooks/hook-checksecurity/src/cli.ts +601 -0
- package/hooks/hook-checksecurity/src/hook.ts +334 -0
- package/hooks/hook-checksecurity/tsconfig.json +15 -0
- package/hooks/hook-checktasks/README.md +144 -0
- package/hooks/hook-checktasks/package.json +55 -0
- package/hooks/hook-checktasks/src/cli.ts +578 -0
- package/hooks/hook-checktasks/src/hook.ts +308 -0
- package/hooks/hook-checktasks/tsconfig.json +20 -0
- package/hooks/hook-checktests/LICENSE +21 -0
- package/hooks/hook-checktests/README.md +137 -0
- package/hooks/hook-checktests/package.json +57 -0
- package/hooks/hook-checktests/src/cli.ts +627 -0
- package/hooks/hook-checktests/src/hook.ts +334 -0
- package/hooks/hook-checktests/tsconfig.json +15 -0
- package/hooks/hook-contextrefresh/CLAUDE.md +23 -0
- package/hooks/hook-contextrefresh/README.md +42 -0
- package/hooks/hook-contextrefresh/package.json +42 -0
- package/hooks/hook-contextrefresh/src/cli.ts +152 -0
- package/hooks/hook-contextrefresh/src/hook.ts +148 -0
- package/hooks/hook-contextrefresh/tsconfig.json +25 -0
- package/hooks/hook-gitguard/CLAUDE.md +22 -0
- package/hooks/hook-gitguard/README.md +30 -0
- package/hooks/hook-gitguard/package.json +57 -0
- package/hooks/hook-gitguard/src/cli.ts +159 -0
- package/hooks/hook-gitguard/src/hook.ts +129 -0
- package/hooks/hook-gitguard/tsconfig.json +25 -0
- package/hooks/hook-packageage/CLAUDE.md +23 -0
- package/hooks/hook-packageage/README.md +33 -0
- package/hooks/hook-packageage/package.json +42 -0
- package/hooks/hook-packageage/src/cli.ts +165 -0
- package/hooks/hook-packageage/src/hook.ts +177 -0
- package/hooks/hook-packageage/tsconfig.json +25 -0
- package/hooks/hook-phonenotify/CLAUDE.md +25 -0
- package/hooks/hook-phonenotify/README.md +44 -0
- package/hooks/hook-phonenotify/package.json +42 -0
- package/hooks/hook-phonenotify/src/cli.ts +196 -0
- package/hooks/hook-phonenotify/src/hook.ts +139 -0
- package/hooks/hook-phonenotify/tsconfig.json +25 -0
- package/hooks/hook-precompact/CLAUDE.md +23 -0
- package/hooks/hook-precompact/README.md +36 -0
- package/hooks/hook-precompact/package.json +42 -0
- package/hooks/hook-precompact/src/cli.ts +168 -0
- package/hooks/hook-precompact/src/hook.ts +122 -0
- package/hooks/hook-precompact/tsconfig.json +25 -0
- package/package.json +61 -0
- package/src/cli/components/App.tsx +191 -0
- package/src/cli/components/CategorySelect.tsx +37 -0
- package/src/cli/components/DataTable.tsx +133 -0
- package/src/cli/components/Header.tsx +18 -0
- package/src/cli/components/HookSelect.tsx +29 -0
- package/src/cli/components/InstallProgress.tsx +105 -0
- package/src/cli/components/SearchView.tsx +86 -0
- package/src/cli/index.tsx +218 -0
- package/src/index.ts +31 -0
- package/src/lib/installer.ts +288 -0
- package/src/lib/registry.ts +205 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code Hook: check-bugs
|
|
5
|
+
*
|
|
6
|
+
* Runs a headless Codex agent to check for potential bugs.
|
|
7
|
+
* Uses service-implementation CLI to dispatch tasks.
|
|
8
|
+
*
|
|
9
|
+
* This hook runs ASYNC (non-blocking) on PostToolUse.
|
|
10
|
+
* Only runs for repos matching [prefix]-[name] pattern.
|
|
11
|
+
*
|
|
12
|
+
* Configuration:
|
|
13
|
+
* - taskListId: task list for dispatching bug tasks
|
|
14
|
+
* - editThreshold: run check after this many edits (default: 3, range: 3-7)
|
|
15
|
+
* - keywords: keywords that trigger the check (default: ["dev"])
|
|
16
|
+
* - enabled: enable/disable the hook
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
import { homedir } from "os";
|
|
22
|
+
import { spawn } from "child_process";
|
|
23
|
+
|
|
24
|
+
interface CheckBugsConfig {
|
|
25
|
+
taskListId?: string;
|
|
26
|
+
editThreshold?: number;
|
|
27
|
+
keywords?: string[];
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface HookInput {
|
|
32
|
+
session_id: string;
|
|
33
|
+
transcript_path: string;
|
|
34
|
+
cwd: string;
|
|
35
|
+
tool_name: string;
|
|
36
|
+
tool_input: Record<string, unknown>;
|
|
37
|
+
tool_output?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface SessionState {
|
|
41
|
+
editCount: number;
|
|
42
|
+
editedFiles: string[];
|
|
43
|
+
lastCheckRun: number;
|
|
44
|
+
checkInProgress: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const CONFIG_KEY = "checkBugsConfig";
|
|
48
|
+
const STATE_DIR = join(homedir(), ".claude", "hook-state");
|
|
49
|
+
const EDIT_TOOLS = ["Edit", "Write", "NotebookEdit"];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sanitize ID to prevent path traversal and injection attacks
|
|
53
|
+
*/
|
|
54
|
+
function sanitizeId(id: string): string {
|
|
55
|
+
if (!id || typeof id !== 'string') return 'default';
|
|
56
|
+
// Only allow alphanumeric, dash, underscore
|
|
57
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 100) || 'default';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sanitize file path for safe display in prompts
|
|
62
|
+
*/
|
|
63
|
+
function sanitizePath(path: string): string {
|
|
64
|
+
if (!path || typeof path !== 'string') return '';
|
|
65
|
+
// Remove any shell special characters that could be used for injection
|
|
66
|
+
return path.replace(/[`$"'\\;&|<>(){}[\]!#*?~]/g, '_');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const BUGS_PROMPT = `You are a code reviewer focused on finding bugs. Review the following files that were recently edited and identify potential bugs:
|
|
70
|
+
|
|
71
|
+
FILES TO REVIEW:
|
|
72
|
+
{files}
|
|
73
|
+
|
|
74
|
+
For each bug found, create a task using the service-implementation CLI:
|
|
75
|
+
service-implementation task dispatch "{taskListId}" -s "BUG: [severity] - [brief description]" -d "[detailed description with file:line reference and suggested fix]"
|
|
76
|
+
|
|
77
|
+
Severity levels: CRITICAL, HIGH, MEDIUM, LOW
|
|
78
|
+
|
|
79
|
+
Focus on:
|
|
80
|
+
- Logic errors and off-by-one errors
|
|
81
|
+
- Null/undefined reference issues
|
|
82
|
+
- Race conditions and async bugs
|
|
83
|
+
- Memory leaks
|
|
84
|
+
- Unhandled edge cases
|
|
85
|
+
- Type mismatches
|
|
86
|
+
- Incorrect error handling
|
|
87
|
+
- Security vulnerabilities
|
|
88
|
+
- Performance issues
|
|
89
|
+
- Resource cleanup issues
|
|
90
|
+
|
|
91
|
+
If no bugs are found, do not create any tasks.
|
|
92
|
+
Only report meaningful bugs, not style issues or minor nitpicks.
|
|
93
|
+
Limit to max 5 most important bugs.`;
|
|
94
|
+
|
|
95
|
+
function isValidRepoPattern(cwd: string): boolean {
|
|
96
|
+
const dirName = cwd.split("/").filter(Boolean).pop() || "";
|
|
97
|
+
// Match: hook-checklint, skill-installhook, iapp-mail, etc.
|
|
98
|
+
return /^[a-z]+-[a-z0-9-]+$/i.test(dirName);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function readStdinJson(): HookInput | null {
|
|
102
|
+
try {
|
|
103
|
+
const stdin = readFileSync(0, "utf-8");
|
|
104
|
+
return JSON.parse(stdin);
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function readSettings(path: string): Record<string, unknown> {
|
|
111
|
+
if (!existsSync(path)) return {};
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
114
|
+
} catch {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getConfig(cwd: string): CheckBugsConfig {
|
|
120
|
+
// Try project settings first
|
|
121
|
+
const projectSettings = readSettings(join(cwd, ".claude", "settings.json"));
|
|
122
|
+
if (projectSettings[CONFIG_KEY]) {
|
|
123
|
+
return projectSettings[CONFIG_KEY] as CheckBugsConfig;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fall back to global settings
|
|
127
|
+
const globalSettings = readSettings(join(homedir(), ".claude", "settings.json"));
|
|
128
|
+
if (globalSettings[CONFIG_KEY]) {
|
|
129
|
+
return globalSettings[CONFIG_KEY] as CheckBugsConfig;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Default config
|
|
133
|
+
return {
|
|
134
|
+
editThreshold: 3,
|
|
135
|
+
keywords: ["dev"],
|
|
136
|
+
enabled: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getStateFile(sessionId: string): string {
|
|
141
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
142
|
+
const safeSessionId = sanitizeId(sessionId);
|
|
143
|
+
return join(STATE_DIR, `checkbugs-${safeSessionId}.json`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getSessionState(sessionId: string): SessionState {
|
|
147
|
+
const stateFile = getStateFile(sessionId);
|
|
148
|
+
if (existsSync(stateFile)) {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(readFileSync(stateFile, "utf-8"));
|
|
151
|
+
} catch {
|
|
152
|
+
// Corrupted state, reset
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { editCount: 0, editedFiles: [], lastCheckRun: 0, checkInProgress: false };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function saveSessionState(sessionId: string, state: SessionState): void {
|
|
159
|
+
const stateFile = getStateFile(sessionId);
|
|
160
|
+
writeFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getSessionName(transcriptPath: string): string | null {
|
|
164
|
+
if (!existsSync(transcriptPath)) return null;
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const content = readFileSync(transcriptPath, "utf-8");
|
|
168
|
+
let lastTitle: string | null = null;
|
|
169
|
+
let searchStart = 0;
|
|
170
|
+
|
|
171
|
+
while (true) {
|
|
172
|
+
const titleIndex = content.indexOf('"custom-title"', searchStart);
|
|
173
|
+
if (titleIndex === -1) break;
|
|
174
|
+
|
|
175
|
+
const lineStart = content.lastIndexOf("\n", titleIndex) + 1;
|
|
176
|
+
const lineEnd = content.indexOf("\n", titleIndex);
|
|
177
|
+
const line = content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const entry = JSON.parse(line);
|
|
181
|
+
if (entry.type === "custom-title" && entry.customTitle) {
|
|
182
|
+
lastTitle = entry.customTitle;
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
// Skip malformed lines
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
searchStart = titleIndex + 1;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return lastTitle;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getProjectTaskListId(cwd: string): string | null {
|
|
198
|
+
const dirName = cwd.split("/").filter(Boolean).pop() || "";
|
|
199
|
+
return `${dirName}-bugfixes`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function runHeadlessBugsCheck(
|
|
203
|
+
cwd: string,
|
|
204
|
+
files: string[],
|
|
205
|
+
taskListId: string
|
|
206
|
+
): void {
|
|
207
|
+
// Sanitize file paths to prevent prompt injection
|
|
208
|
+
const filesFormatted = files.map((f) => `- ${sanitizePath(f)}`).join("\n");
|
|
209
|
+
// Sanitize taskListId
|
|
210
|
+
const safeTaskListId = sanitizeId(taskListId);
|
|
211
|
+
|
|
212
|
+
const prompt = BUGS_PROMPT
|
|
213
|
+
.replace("{files}", filesFormatted)
|
|
214
|
+
.replace("{taskListId}", safeTaskListId);
|
|
215
|
+
|
|
216
|
+
// Spawn headless Codex agent in background
|
|
217
|
+
const child = spawn(
|
|
218
|
+
"codex",
|
|
219
|
+
[
|
|
220
|
+
"exec",
|
|
221
|
+
prompt,
|
|
222
|
+
],
|
|
223
|
+
{
|
|
224
|
+
cwd,
|
|
225
|
+
detached: true,
|
|
226
|
+
stdio: "ignore",
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// Detach from parent process
|
|
231
|
+
child.unref();
|
|
232
|
+
|
|
233
|
+
console.error(`[hook-checkbugs] Started bugs check for ${files.length} files`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function approve() {
|
|
237
|
+
console.log(JSON.stringify({ decision: "approve" }));
|
|
238
|
+
process.exit(0);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function run() {
|
|
242
|
+
const hookInput = readStdinJson();
|
|
243
|
+
if (!hookInput) {
|
|
244
|
+
approve();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const { session_id, cwd, tool_name, tool_input, transcript_path } = hookInput;
|
|
249
|
+
|
|
250
|
+
// Only process edit tools
|
|
251
|
+
if (!EDIT_TOOLS.includes(tool_name)) {
|
|
252
|
+
approve();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Check repo pattern - only run for [prefix]-[name] folders
|
|
257
|
+
if (!isValidRepoPattern(cwd)) {
|
|
258
|
+
approve();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const config = getConfig(cwd);
|
|
263
|
+
|
|
264
|
+
// Check if hook is disabled
|
|
265
|
+
if (config.enabled === false) {
|
|
266
|
+
approve();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Check keywords match
|
|
271
|
+
const sessionName = transcript_path ? getSessionName(transcript_path) : null;
|
|
272
|
+
const nameToCheck = sessionName || config.taskListId || "";
|
|
273
|
+
const keywords = config.keywords || ["dev"];
|
|
274
|
+
|
|
275
|
+
const matchesKeyword = keywords.some((keyword) =>
|
|
276
|
+
nameToCheck.toLowerCase().includes(keyword.toLowerCase())
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// If keywords are configured and we have a session name, check for match
|
|
280
|
+
// Empty nameToCheck means we can't filter, so allow the check to proceed
|
|
281
|
+
if (keywords.length > 0 && nameToCheck && !matchesKeyword) {
|
|
282
|
+
approve();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Get edited file path
|
|
287
|
+
const filePath = (tool_input.file_path || tool_input.notebook_path) as string | undefined;
|
|
288
|
+
if (!filePath) {
|
|
289
|
+
approve();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Update session state
|
|
294
|
+
const state = getSessionState(session_id);
|
|
295
|
+
|
|
296
|
+
// Skip if check already in progress
|
|
297
|
+
if (state.checkInProgress) {
|
|
298
|
+
approve();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
state.editCount++;
|
|
303
|
+
|
|
304
|
+
if (!state.editedFiles.includes(filePath)) {
|
|
305
|
+
state.editedFiles.push(filePath);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const threshold = Math.min(7, Math.max(3, config.editThreshold || 3));
|
|
309
|
+
|
|
310
|
+
// Check if we should run bugs check
|
|
311
|
+
if (state.editCount >= threshold) {
|
|
312
|
+
const taskListId = config.taskListId || getProjectTaskListId(cwd) || "default-bugfixes";
|
|
313
|
+
|
|
314
|
+
// Mark check in progress
|
|
315
|
+
state.checkInProgress = true;
|
|
316
|
+
saveSessionState(session_id, state);
|
|
317
|
+
|
|
318
|
+
// Run headless bugs check (async, non-blocking)
|
|
319
|
+
runHeadlessBugsCheck(cwd, state.editedFiles, taskListId);
|
|
320
|
+
|
|
321
|
+
// Reset counter after starting check
|
|
322
|
+
state.editCount = 0;
|
|
323
|
+
state.editedFiles = [];
|
|
324
|
+
state.lastCheckRun = Date.now();
|
|
325
|
+
state.checkInProgress = false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
saveSessionState(session_id, state);
|
|
329
|
+
approve();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Allow direct execution
|
|
333
|
+
if (import.meta.main) {
|
|
334
|
+
run();
|
|
335
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# @hasnaxyz/hook-checkdocs
|
|
2
|
+
|
|
3
|
+
Claude Code hook that checks for missing documentation and creates tasks via service-implementation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Async execution**: Runs in background, non-blocking
|
|
8
|
+
- **Headless Claude agent**: Spawns `claude -p` to check documentation
|
|
9
|
+
- **Task dispatch**: Creates tasks via `service-implementation task dispatch`
|
|
10
|
+
- **Configurable threshold**: Check after N file edits (3-7, default: 3)
|
|
11
|
+
- **Repo pattern check**: Only runs for repos matching `[prefix]-[name]` pattern
|
|
12
|
+
- **Session-aware**: Only runs for sessions matching configured keywords
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### Global CLI
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun add -g @hasnaxyz/hook-checkdocs
|
|
20
|
+
hook-checkdocs install --global
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Project-specific
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd /path/to/your/project
|
|
27
|
+
bunx @hasnaxyz/hook-checkdocs install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- `claude` CLI (for headless agent)
|
|
33
|
+
- `service-implementation` CLI (for task dispatch)
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
Once installed, the hook runs automatically after file edits (Edit, Write, NotebookEdit tools).
|
|
38
|
+
|
|
39
|
+
### Commands
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
hook-checkdocs install [path] # Install the hook
|
|
43
|
+
hook-checkdocs config [path] # Update configuration
|
|
44
|
+
hook-checkdocs uninstall [path] # Remove the hook
|
|
45
|
+
hook-checkdocs status # Show hook status
|
|
46
|
+
hook-checkdocs run # Execute hook (called by Claude Code)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Options
|
|
50
|
+
|
|
51
|
+
- `--global`, `-g`: Apply to global settings (`~/.claude/settings.json`)
|
|
52
|
+
- `/path/to/repo`: Apply to specific project path
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
Configuration is stored in `.claude/settings.json`:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"hooks": {
|
|
61
|
+
"PostToolUse": [{
|
|
62
|
+
"matcher": {
|
|
63
|
+
"tool_name": "^(Edit|Write|NotebookEdit)$"
|
|
64
|
+
},
|
|
65
|
+
"hooks": [{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "bunx @hasnaxyz/hook-checkdocs@latest run",
|
|
68
|
+
"timeout": 120,
|
|
69
|
+
"async": true
|
|
70
|
+
}]
|
|
71
|
+
}]
|
|
72
|
+
},
|
|
73
|
+
"checkDocsConfig": {
|
|
74
|
+
"editThreshold": 3,
|
|
75
|
+
"taskListId": "myproject-dev",
|
|
76
|
+
"keywords": ["dev"],
|
|
77
|
+
"enabled": true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Options
|
|
83
|
+
|
|
84
|
+
| Option | Type | Default | Description |
|
|
85
|
+
|--------|------|---------|-------------|
|
|
86
|
+
| `editThreshold` | number | 3 | Run check after this many edits (3-7) |
|
|
87
|
+
| `taskListId` | string | auto | Task list for dispatching tasks |
|
|
88
|
+
| `keywords` | string[] | ["dev"] | Only run for matching sessions |
|
|
89
|
+
| `enabled` | boolean | true | Enable/disable the hook |
|
|
90
|
+
|
|
91
|
+
## How It Works
|
|
92
|
+
|
|
93
|
+
1. **Tracks file edits**: Monitors Edit, Write, NotebookEdit tool calls
|
|
94
|
+
2. **Validates repo**: Only runs for repos matching `[prefix]-[name]` pattern
|
|
95
|
+
3. **Counts edits**: Maintains per-session edit counter
|
|
96
|
+
4. **Triggers check**: After N edits, spawns headless Claude agent
|
|
97
|
+
5. **Agent reviews**: Claude analyzes files for missing documentation
|
|
98
|
+
6. **Creates tasks**: Uses `service-implementation task dispatch` for each issue
|
|
99
|
+
7. **Non-blocking**: Runs async, doesn't block main session
|
|
100
|
+
|
|
101
|
+
## Documentation Issues Detected
|
|
102
|
+
|
|
103
|
+
The hook checks for:
|
|
104
|
+
|
|
105
|
+
- Missing function/method documentation
|
|
106
|
+
- Outdated README sections
|
|
107
|
+
- Missing API documentation
|
|
108
|
+
- Missing inline comments for complex logic
|
|
109
|
+
- Missing type definitions documentation
|
|
110
|
+
- Missing usage examples
|
|
111
|
+
|
|
112
|
+
## Task Format
|
|
113
|
+
|
|
114
|
+
Tasks are dispatched with:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
service-implementation task dispatch "myproject-dev" \
|
|
118
|
+
-s "DOCS: [brief description]" \
|
|
119
|
+
-d "[detailed description of what docs need to be added/updated]"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Session State
|
|
123
|
+
|
|
124
|
+
State is persisted in `~/.claude/hook-state/checkdocs-{session_id}.json`:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"editCount": 2,
|
|
129
|
+
"editedFiles": ["src/file.ts", "src/other.ts"],
|
|
130
|
+
"lastCheckRun": 1706500000000,
|
|
131
|
+
"checkInProgress": false
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasnaxyz/hook-checkdocs",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "Claude Code hook that checks for missing documentation and creates tasks",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"hook-checkdocs": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/hook.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/hook.js",
|
|
13
|
+
"types": "./dist/hook.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./cli": {
|
|
16
|
+
"import": "./dist/cli.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "bun build ./src/cli.ts ./src/hook.ts --outdir ./dist --target node",
|
|
25
|
+
"prepublishOnly": "bun run build",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"claude-code",
|
|
30
|
+
"claude",
|
|
31
|
+
"hook",
|
|
32
|
+
"docs",
|
|
33
|
+
"documentation",
|
|
34
|
+
"headless",
|
|
35
|
+
"automation",
|
|
36
|
+
"cli"
|
|
37
|
+
],
|
|
38
|
+
"author": "Hasna",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/hasnaxyz/hook-checkdocs.git"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "restricted",
|
|
46
|
+
"registry": "https://registry.npmjs.org/"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18",
|
|
50
|
+
"bun": ">=1.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/bun": "^1.3.8",
|
|
54
|
+
"@types/node": "^20",
|
|
55
|
+
"typescript": "^5.0.0"
|
|
56
|
+
}
|
|
57
|
+
}
|