@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,310 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code Hook: check-docs
|
|
5
|
+
*
|
|
6
|
+
* Runs a headless Claude Code agent to check for missing documentation.
|
|
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 doc 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 CheckDocsConfig {
|
|
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 = "checkDocsConfig";
|
|
48
|
+
const STATE_DIR = join(homedir(), ".claude", "hook-state");
|
|
49
|
+
const EDIT_TOOLS = ["Edit", "Write", "NotebookEdit"];
|
|
50
|
+
|
|
51
|
+
const DOCS_PROMPT = `You are a documentation reviewer. Review the following files that were recently edited and identify missing or outdated documentation:
|
|
52
|
+
|
|
53
|
+
FILES TO REVIEW:
|
|
54
|
+
{files}
|
|
55
|
+
|
|
56
|
+
For each documentation issue found, create a task using the service-implementation CLI:
|
|
57
|
+
service-implementation task dispatch "{taskListId}" -s "DOCS: [brief description]" -d "[detailed description of what docs need to be added/updated]"
|
|
58
|
+
|
|
59
|
+
Focus on:
|
|
60
|
+
- Missing function/method documentation
|
|
61
|
+
- Outdated README sections
|
|
62
|
+
- Missing API documentation
|
|
63
|
+
- Missing inline comments for complex logic
|
|
64
|
+
- Missing type definitions documentation
|
|
65
|
+
- Missing usage examples
|
|
66
|
+
|
|
67
|
+
If no documentation issues are found, do not create any tasks.
|
|
68
|
+
Only create tasks for meaningful documentation gaps, not trivial ones.
|
|
69
|
+
Limit to max 5 most important documentation tasks.`;
|
|
70
|
+
|
|
71
|
+
function isValidRepoPattern(cwd: string): boolean {
|
|
72
|
+
const dirName = cwd.split("/").filter(Boolean).pop() || "";
|
|
73
|
+
// Match: hook-checklint, skill-installhook, iapp-mail, etc.
|
|
74
|
+
return /^[a-z]+-[a-z0-9-]+$/i.test(dirName);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readStdinJson(): HookInput | null {
|
|
78
|
+
try {
|
|
79
|
+
const stdin = readFileSync(0, "utf-8");
|
|
80
|
+
return JSON.parse(stdin);
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function readSettings(path: string): Record<string, unknown> {
|
|
87
|
+
if (!existsSync(path)) return {};
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
90
|
+
} catch {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getConfig(cwd: string): CheckDocsConfig {
|
|
96
|
+
// Try project settings first
|
|
97
|
+
const projectSettings = readSettings(join(cwd, ".claude", "settings.json"));
|
|
98
|
+
if (projectSettings[CONFIG_KEY]) {
|
|
99
|
+
return projectSettings[CONFIG_KEY] as CheckDocsConfig;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Fall back to global settings
|
|
103
|
+
const globalSettings = readSettings(join(homedir(), ".claude", "settings.json"));
|
|
104
|
+
if (globalSettings[CONFIG_KEY]) {
|
|
105
|
+
return globalSettings[CONFIG_KEY] as CheckDocsConfig;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Default config
|
|
109
|
+
return {
|
|
110
|
+
editThreshold: 3,
|
|
111
|
+
keywords: ["dev"],
|
|
112
|
+
enabled: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getStateFile(sessionId: string): string {
|
|
117
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
118
|
+
return join(STATE_DIR, `checkdocs-${sessionId}.json`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getSessionState(sessionId: string): SessionState {
|
|
122
|
+
const stateFile = getStateFile(sessionId);
|
|
123
|
+
if (existsSync(stateFile)) {
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(readFileSync(stateFile, "utf-8"));
|
|
126
|
+
} catch {
|
|
127
|
+
// Corrupted state, reset
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { editCount: 0, editedFiles: [], lastCheckRun: 0, checkInProgress: false };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function saveSessionState(sessionId: string, state: SessionState): void {
|
|
134
|
+
const stateFile = getStateFile(sessionId);
|
|
135
|
+
writeFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getSessionName(transcriptPath: string): string | null {
|
|
139
|
+
if (!existsSync(transcriptPath)) return null;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const content = readFileSync(transcriptPath, "utf-8");
|
|
143
|
+
let lastTitle: string | null = null;
|
|
144
|
+
let searchStart = 0;
|
|
145
|
+
|
|
146
|
+
while (true) {
|
|
147
|
+
const titleIndex = content.indexOf('"custom-title"', searchStart);
|
|
148
|
+
if (titleIndex === -1) break;
|
|
149
|
+
|
|
150
|
+
const lineStart = content.lastIndexOf("\n", titleIndex) + 1;
|
|
151
|
+
const lineEnd = content.indexOf("\n", titleIndex);
|
|
152
|
+
const line = content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const entry = JSON.parse(line);
|
|
156
|
+
if (entry.type === "custom-title" && entry.customTitle) {
|
|
157
|
+
lastTitle = entry.customTitle;
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Skip malformed lines
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
searchStart = titleIndex + 1;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return lastTitle;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getProjectTaskListId(cwd: string): string | null {
|
|
173
|
+
const dirName = cwd.split("/").filter(Boolean).pop() || "";
|
|
174
|
+
return `${dirName}-dev`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function runHeadlessDocsCheck(
|
|
178
|
+
cwd: string,
|
|
179
|
+
files: string[],
|
|
180
|
+
taskListId: string
|
|
181
|
+
): void {
|
|
182
|
+
const filesFormatted = files.map((f) => `- ${f}`).join("\n");
|
|
183
|
+
|
|
184
|
+
const prompt = DOCS_PROMPT
|
|
185
|
+
.replace("{files}", filesFormatted)
|
|
186
|
+
.replace("{taskListId}", taskListId);
|
|
187
|
+
|
|
188
|
+
// Spawn headless Claude Code agent in background
|
|
189
|
+
const child = spawn(
|
|
190
|
+
"claude",
|
|
191
|
+
[
|
|
192
|
+
"-p",
|
|
193
|
+
prompt,
|
|
194
|
+
"--permission-mode",
|
|
195
|
+
"acceptEdits",
|
|
196
|
+
"--allowedTools",
|
|
197
|
+
"Bash,Read",
|
|
198
|
+
"--no-session-persistence",
|
|
199
|
+
],
|
|
200
|
+
{
|
|
201
|
+
cwd,
|
|
202
|
+
detached: true,
|
|
203
|
+
stdio: "ignore",
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Detach from parent process
|
|
208
|
+
child.unref();
|
|
209
|
+
|
|
210
|
+
console.error(`[hook-checkdocs] Started docs check for ${files.length} files`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function approve() {
|
|
214
|
+
console.log(JSON.stringify({ decision: "approve" }));
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function run() {
|
|
219
|
+
const hookInput = readStdinJson();
|
|
220
|
+
if (!hookInput) {
|
|
221
|
+
approve();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const { session_id, cwd, tool_name, tool_input, transcript_path } = hookInput;
|
|
226
|
+
|
|
227
|
+
// Only process edit tools
|
|
228
|
+
if (!EDIT_TOOLS.includes(tool_name)) {
|
|
229
|
+
approve();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check repo pattern - only run for [prefix]-[name] folders
|
|
234
|
+
if (!isValidRepoPattern(cwd)) {
|
|
235
|
+
approve();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const config = getConfig(cwd);
|
|
240
|
+
|
|
241
|
+
// Check if hook is disabled
|
|
242
|
+
if (config.enabled === false) {
|
|
243
|
+
approve();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check keywords match
|
|
248
|
+
const sessionName = transcript_path ? getSessionName(transcript_path) : null;
|
|
249
|
+
const nameToCheck = sessionName || config.taskListId || "";
|
|
250
|
+
const keywords = config.keywords || ["dev"];
|
|
251
|
+
|
|
252
|
+
const matchesKeyword = keywords.some((keyword) =>
|
|
253
|
+
nameToCheck.toLowerCase().includes(keyword.toLowerCase())
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
if (!matchesKeyword && keywords.length > 0 && nameToCheck) {
|
|
257
|
+
approve();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Get edited file path
|
|
262
|
+
const filePath = (tool_input.file_path || tool_input.notebook_path) as string | undefined;
|
|
263
|
+
if (!filePath) {
|
|
264
|
+
approve();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Update session state
|
|
269
|
+
const state = getSessionState(session_id);
|
|
270
|
+
|
|
271
|
+
// Skip if check already in progress
|
|
272
|
+
if (state.checkInProgress) {
|
|
273
|
+
approve();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
state.editCount++;
|
|
278
|
+
|
|
279
|
+
if (!state.editedFiles.includes(filePath)) {
|
|
280
|
+
state.editedFiles.push(filePath);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const threshold = Math.min(7, Math.max(3, config.editThreshold || 3));
|
|
284
|
+
|
|
285
|
+
// Check if we should run docs check
|
|
286
|
+
if (state.editCount >= threshold) {
|
|
287
|
+
const taskListId = config.taskListId || getProjectTaskListId(cwd) || "default-dev";
|
|
288
|
+
|
|
289
|
+
// Mark check in progress
|
|
290
|
+
state.checkInProgress = true;
|
|
291
|
+
saveSessionState(session_id, state);
|
|
292
|
+
|
|
293
|
+
// Run headless docs check (async, non-blocking)
|
|
294
|
+
runHeadlessDocsCheck(cwd, state.editedFiles, taskListId);
|
|
295
|
+
|
|
296
|
+
// Reset counter after starting check
|
|
297
|
+
state.editCount = 0;
|
|
298
|
+
state.editedFiles = [];
|
|
299
|
+
state.lastCheckRun = Date.now();
|
|
300
|
+
state.checkInProgress = false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
saveSessionState(session_id, state);
|
|
304
|
+
approve();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Allow direct execution
|
|
308
|
+
if (import.meta.main) {
|
|
309
|
+
run();
|
|
310
|
+
}
|
|
@@ -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,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hasna
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @hasnaxyz/hook-checkfiles
|
|
2
|
+
|
|
3
|
+
Claude Code hook that runs a headless agent to review files and create tasks via service-implementation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Async execution**: Runs in background, non-blocking
|
|
8
|
+
- **Headless Claude agent**: Spawns `claude -p` to review files
|
|
9
|
+
- **Task dispatch**: Creates tasks via `service-implementation task dispatch`
|
|
10
|
+
- **Configurable threshold**: Review after N file edits (3-7, default: 3)
|
|
11
|
+
- **Session-aware**: Only runs for sessions matching configured keywords
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Global CLI
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add -g @hasnaxyz/hook-checkfiles
|
|
19
|
+
hook-checkfiles install --global
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Project-specific
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd /path/to/your/project
|
|
26
|
+
bunx @hasnaxyz/hook-checkfiles install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
- `claude` CLI (for headless agent)
|
|
32
|
+
- `service-implementation` CLI (for task dispatch)
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Once installed, the hook runs automatically after file edits (Edit, Write, NotebookEdit tools).
|
|
37
|
+
|
|
38
|
+
### Commands
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
hook-checkfiles install [path] # Install the hook
|
|
42
|
+
hook-checkfiles config [path] # Update configuration
|
|
43
|
+
hook-checkfiles uninstall [path] # Remove the hook
|
|
44
|
+
hook-checkfiles status # Show hook status
|
|
45
|
+
hook-checkfiles run # Execute hook (called by Claude Code)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Options
|
|
49
|
+
|
|
50
|
+
- `--global`, `-g`: Apply to global settings (`~/.claude/settings.json`)
|
|
51
|
+
- `/path/to/repo`: Apply to specific project path
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
Configuration is stored in `.claude/settings.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"hooks": {
|
|
60
|
+
"PostToolUse": [{
|
|
61
|
+
"matcher": {
|
|
62
|
+
"tool_name": "^(Edit|Write|NotebookEdit)$"
|
|
63
|
+
},
|
|
64
|
+
"hooks": [{
|
|
65
|
+
"type": "command",
|
|
66
|
+
"command": "bunx @hasnaxyz/hook-checkfiles@latest run",
|
|
67
|
+
"timeout": 120,
|
|
68
|
+
"async": true
|
|
69
|
+
}]
|
|
70
|
+
}]
|
|
71
|
+
},
|
|
72
|
+
"checkFilesConfig": {
|
|
73
|
+
"editThreshold": 3,
|
|
74
|
+
"taskListId": "myproject-bugfixes",
|
|
75
|
+
"keywords": ["dev"],
|
|
76
|
+
"enabled": true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Options
|
|
82
|
+
|
|
83
|
+
| Option | Type | Default | Description |
|
|
84
|
+
|--------|------|---------|-------------|
|
|
85
|
+
| `editThreshold` | number | 3 | Run review after this many edits (3-7) |
|
|
86
|
+
| `taskListId` | string | auto | Task list for dispatching tasks |
|
|
87
|
+
| `keywords` | string[] | ["dev"] | Only run for matching sessions |
|
|
88
|
+
| `reviewPrompt` | string | default | Custom prompt for headless agent |
|
|
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. **Counts edits**: Maintains per-session edit counter
|
|
95
|
+
3. **Triggers review**: After N edits, spawns headless Claude agent
|
|
96
|
+
4. **Agent reviews**: Claude analyzes files for issues
|
|
97
|
+
5. **Creates tasks**: Uses `service-implementation task dispatch` for each issue
|
|
98
|
+
6. **Non-blocking**: Runs async, doesn't block main session
|
|
99
|
+
|
|
100
|
+
## Headless Agent
|
|
101
|
+
|
|
102
|
+
The hook spawns:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
claude -p "<review prompt>" \
|
|
106
|
+
--permission-mode acceptEdits \
|
|
107
|
+
--allowedTools "Bash,Read" \
|
|
108
|
+
--no-session-persistence
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The agent:
|
|
112
|
+
- Reads the edited files
|
|
113
|
+
- Identifies bugs, security issues, performance problems
|
|
114
|
+
- Creates tasks via `service-implementation task dispatch`
|
|
115
|
+
|
|
116
|
+
## Task Format
|
|
117
|
+
|
|
118
|
+
Tasks are dispatched with:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
service-implementation task dispatch "myproject-bugfixes" \
|
|
122
|
+
-s "REVIEW: [brief issue description]" \
|
|
123
|
+
-d "[detailed description with file:line reference]"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Session State
|
|
127
|
+
|
|
128
|
+
State is persisted in `~/.claude/hook-state/checkfiles-{session_id}.json`:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"editCount": 2,
|
|
133
|
+
"editedFiles": ["src/file.ts", "src/other.ts"],
|
|
134
|
+
"lastReviewRun": 1706500000000,
|
|
135
|
+
"reviewInProgress": false
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasnaxyz/hook-checkfiles",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Claude Code hook that runs headless agent to review files and create tasks",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"hook-checkfiles": "./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
|
+
"review",
|
|
33
|
+
"headless",
|
|
34
|
+
"automation",
|
|
35
|
+
"cli"
|
|
36
|
+
],
|
|
37
|
+
"author": "Hasna",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/hasnaxyz/hook-checkfiles.git"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "restricted",
|
|
45
|
+
"registry": "https://registry.npmjs.org/"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18",
|
|
49
|
+
"bun": ">=1.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/bun": "^1.3.8",
|
|
53
|
+
"@types/node": "^20",
|
|
54
|
+
"typescript": "^5.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|