@hasna/hooks 0.0.1 → 0.0.2

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.
Files changed (62) hide show
  1. package/dist/index.js +366 -0
  2. package/hooks/hook-agentmessages/bin/cli.ts +125 -0
  3. package/package.json +2 -2
  4. package/hooks/hook-agentmessages/src/check-messages.ts +0 -151
  5. package/hooks/hook-agentmessages/src/install.ts +0 -126
  6. package/hooks/hook-agentmessages/src/session-start.ts +0 -255
  7. package/hooks/hook-agentmessages/src/uninstall.ts +0 -89
  8. package/hooks/hook-branchprotect/src/cli.ts +0 -126
  9. package/hooks/hook-branchprotect/src/hook.ts +0 -88
  10. package/hooks/hook-branchprotect/tsconfig.json +0 -25
  11. package/hooks/hook-checkbugs/src/cli.ts +0 -628
  12. package/hooks/hook-checkbugs/src/hook.ts +0 -335
  13. package/hooks/hook-checkbugs/tsconfig.json +0 -15
  14. package/hooks/hook-checkdocs/src/cli.ts +0 -628
  15. package/hooks/hook-checkdocs/src/hook.ts +0 -310
  16. package/hooks/hook-checkdocs/tsconfig.json +0 -15
  17. package/hooks/hook-checkfiles/src/cli.ts +0 -545
  18. package/hooks/hook-checkfiles/src/hook.ts +0 -321
  19. package/hooks/hook-checkfiles/tsconfig.json +0 -15
  20. package/hooks/hook-checklint/src/cli-patch.ts +0 -32
  21. package/hooks/hook-checklint/src/cli.ts +0 -667
  22. package/hooks/hook-checklint/src/hook.ts +0 -473
  23. package/hooks/hook-checklint/tsconfig.json +0 -15
  24. package/hooks/hook-checkpoint/src/cli.ts +0 -191
  25. package/hooks/hook-checkpoint/src/hook.ts +0 -207
  26. package/hooks/hook-checkpoint/tsconfig.json +0 -25
  27. package/hooks/hook-checksecurity/src/cli.ts +0 -601
  28. package/hooks/hook-checksecurity/src/hook.ts +0 -334
  29. package/hooks/hook-checksecurity/tsconfig.json +0 -15
  30. package/hooks/hook-checktasks/src/cli.ts +0 -578
  31. package/hooks/hook-checktasks/src/hook.ts +0 -308
  32. package/hooks/hook-checktasks/tsconfig.json +0 -20
  33. package/hooks/hook-checktests/src/cli.ts +0 -627
  34. package/hooks/hook-checktests/src/hook.ts +0 -334
  35. package/hooks/hook-checktests/tsconfig.json +0 -15
  36. package/hooks/hook-contextrefresh/src/cli.ts +0 -152
  37. package/hooks/hook-contextrefresh/src/hook.ts +0 -148
  38. package/hooks/hook-contextrefresh/tsconfig.json +0 -25
  39. package/hooks/hook-gitguard/src/cli.ts +0 -159
  40. package/hooks/hook-gitguard/src/hook.ts +0 -129
  41. package/hooks/hook-gitguard/tsconfig.json +0 -25
  42. package/hooks/hook-packageage/src/cli.ts +0 -165
  43. package/hooks/hook-packageage/src/hook.ts +0 -177
  44. package/hooks/hook-packageage/tsconfig.json +0 -25
  45. package/hooks/hook-phonenotify/src/cli.ts +0 -196
  46. package/hooks/hook-phonenotify/src/hook.ts +0 -139
  47. package/hooks/hook-phonenotify/tsconfig.json +0 -25
  48. package/hooks/hook-precompact/src/cli.ts +0 -168
  49. package/hooks/hook-precompact/src/hook.ts +0 -122
  50. package/hooks/hook-precompact/tsconfig.json +0 -25
  51. package/src/cli/components/App.tsx +0 -191
  52. package/src/cli/components/CategorySelect.tsx +0 -37
  53. package/src/cli/components/DataTable.tsx +0 -133
  54. package/src/cli/components/Header.tsx +0 -18
  55. package/src/cli/components/HookSelect.tsx +0 -29
  56. package/src/cli/components/InstallProgress.tsx +0 -105
  57. package/src/cli/components/SearchView.tsx +0 -86
  58. package/src/cli/index.tsx +0 -218
  59. package/src/index.ts +0 -31
  60. package/src/lib/installer.ts +0 -288
  61. package/src/lib/registry.ts +0 -205
  62. package/tsconfig.json +0 -17
@@ -1,335 +0,0 @@
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
- }
@@ -1,15 +0,0 @@
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
- }