@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.
Files changed (110) hide show
  1. package/.npmrc.example +2 -0
  2. package/AGENTS.md +54 -0
  3. package/CLAUDE.md +70 -0
  4. package/CONTRIBUTING.md +45 -0
  5. package/README.md +232 -0
  6. package/bin/index.js +5171 -0
  7. package/hooks/hook-agentmessages/CLAUDE.md +79 -0
  8. package/hooks/hook-agentmessages/LICENSE +21 -0
  9. package/hooks/hook-agentmessages/README.md +107 -0
  10. package/hooks/hook-agentmessages/package.json +31 -0
  11. package/hooks/hook-agentmessages/src/check-messages.ts +151 -0
  12. package/hooks/hook-agentmessages/src/install.ts +126 -0
  13. package/hooks/hook-agentmessages/src/session-start.ts +255 -0
  14. package/hooks/hook-agentmessages/src/uninstall.ts +89 -0
  15. package/hooks/hook-branchprotect/CLAUDE.md +23 -0
  16. package/hooks/hook-branchprotect/README.md +25 -0
  17. package/hooks/hook-branchprotect/package.json +42 -0
  18. package/hooks/hook-branchprotect/src/cli.ts +126 -0
  19. package/hooks/hook-branchprotect/src/hook.ts +88 -0
  20. package/hooks/hook-branchprotect/tsconfig.json +25 -0
  21. package/hooks/hook-checkbugs/LICENSE +21 -0
  22. package/hooks/hook-checkbugs/README.md +140 -0
  23. package/hooks/hook-checkbugs/package.json +58 -0
  24. package/hooks/hook-checkbugs/src/cli.ts +628 -0
  25. package/hooks/hook-checkbugs/src/hook.ts +335 -0
  26. package/hooks/hook-checkbugs/tsconfig.json +15 -0
  27. package/hooks/hook-checkdocs/README.md +137 -0
  28. package/hooks/hook-checkdocs/package.json +57 -0
  29. package/hooks/hook-checkdocs/src/cli.ts +628 -0
  30. package/hooks/hook-checkdocs/src/hook.ts +310 -0
  31. package/hooks/hook-checkdocs/tsconfig.json +15 -0
  32. package/hooks/hook-checkfiles/LICENSE +21 -0
  33. package/hooks/hook-checkfiles/README.md +141 -0
  34. package/hooks/hook-checkfiles/package.json +56 -0
  35. package/hooks/hook-checkfiles/src/cli.ts +545 -0
  36. package/hooks/hook-checkfiles/src/hook.ts +321 -0
  37. package/hooks/hook-checkfiles/tsconfig.json +15 -0
  38. package/hooks/hook-checklint/LICENSE +21 -0
  39. package/hooks/hook-checklint/README.md +147 -0
  40. package/hooks/hook-checklint/package.json +57 -0
  41. package/hooks/hook-checklint/src/cli-patch.ts +32 -0
  42. package/hooks/hook-checklint/src/cli.ts +667 -0
  43. package/hooks/hook-checklint/src/hook.ts +473 -0
  44. package/hooks/hook-checklint/tsconfig.json +15 -0
  45. package/hooks/hook-checkpoint/CLAUDE.md +23 -0
  46. package/hooks/hook-checkpoint/README.md +37 -0
  47. package/hooks/hook-checkpoint/package.json +58 -0
  48. package/hooks/hook-checkpoint/src/cli.ts +191 -0
  49. package/hooks/hook-checkpoint/src/hook.ts +207 -0
  50. package/hooks/hook-checkpoint/tsconfig.json +25 -0
  51. package/hooks/hook-checksecurity/LICENSE +21 -0
  52. package/hooks/hook-checksecurity/README.md +158 -0
  53. package/hooks/hook-checksecurity/package.json +57 -0
  54. package/hooks/hook-checksecurity/src/cli.ts +601 -0
  55. package/hooks/hook-checksecurity/src/hook.ts +334 -0
  56. package/hooks/hook-checksecurity/tsconfig.json +15 -0
  57. package/hooks/hook-checktasks/README.md +144 -0
  58. package/hooks/hook-checktasks/package.json +55 -0
  59. package/hooks/hook-checktasks/src/cli.ts +578 -0
  60. package/hooks/hook-checktasks/src/hook.ts +308 -0
  61. package/hooks/hook-checktasks/tsconfig.json +20 -0
  62. package/hooks/hook-checktests/LICENSE +21 -0
  63. package/hooks/hook-checktests/README.md +137 -0
  64. package/hooks/hook-checktests/package.json +57 -0
  65. package/hooks/hook-checktests/src/cli.ts +627 -0
  66. package/hooks/hook-checktests/src/hook.ts +334 -0
  67. package/hooks/hook-checktests/tsconfig.json +15 -0
  68. package/hooks/hook-contextrefresh/CLAUDE.md +23 -0
  69. package/hooks/hook-contextrefresh/README.md +42 -0
  70. package/hooks/hook-contextrefresh/package.json +42 -0
  71. package/hooks/hook-contextrefresh/src/cli.ts +152 -0
  72. package/hooks/hook-contextrefresh/src/hook.ts +148 -0
  73. package/hooks/hook-contextrefresh/tsconfig.json +25 -0
  74. package/hooks/hook-gitguard/CLAUDE.md +22 -0
  75. package/hooks/hook-gitguard/README.md +30 -0
  76. package/hooks/hook-gitguard/package.json +57 -0
  77. package/hooks/hook-gitguard/src/cli.ts +159 -0
  78. package/hooks/hook-gitguard/src/hook.ts +129 -0
  79. package/hooks/hook-gitguard/tsconfig.json +25 -0
  80. package/hooks/hook-packageage/CLAUDE.md +23 -0
  81. package/hooks/hook-packageage/README.md +33 -0
  82. package/hooks/hook-packageage/package.json +42 -0
  83. package/hooks/hook-packageage/src/cli.ts +165 -0
  84. package/hooks/hook-packageage/src/hook.ts +177 -0
  85. package/hooks/hook-packageage/tsconfig.json +25 -0
  86. package/hooks/hook-phonenotify/CLAUDE.md +25 -0
  87. package/hooks/hook-phonenotify/README.md +44 -0
  88. package/hooks/hook-phonenotify/package.json +42 -0
  89. package/hooks/hook-phonenotify/src/cli.ts +196 -0
  90. package/hooks/hook-phonenotify/src/hook.ts +139 -0
  91. package/hooks/hook-phonenotify/tsconfig.json +25 -0
  92. package/hooks/hook-precompact/CLAUDE.md +23 -0
  93. package/hooks/hook-precompact/README.md +36 -0
  94. package/hooks/hook-precompact/package.json +42 -0
  95. package/hooks/hook-precompact/src/cli.ts +168 -0
  96. package/hooks/hook-precompact/src/hook.ts +122 -0
  97. package/hooks/hook-precompact/tsconfig.json +25 -0
  98. package/package.json +61 -0
  99. package/src/cli/components/App.tsx +191 -0
  100. package/src/cli/components/CategorySelect.tsx +37 -0
  101. package/src/cli/components/DataTable.tsx +133 -0
  102. package/src/cli/components/Header.tsx +18 -0
  103. package/src/cli/components/HookSelect.tsx +29 -0
  104. package/src/cli/components/InstallProgress.tsx +105 -0
  105. package/src/cli/components/SearchView.tsx +86 -0
  106. package/src/cli/index.tsx +218 -0
  107. package/src/index.ts +31 -0
  108. package/src/lib/installer.ts +288 -0
  109. package/src/lib/registry.ts +205 -0
  110. package/tsconfig.json +17 -0
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Claude Code Hook: check-tasks
5
+ *
6
+ * Prevents Claude from stopping when there are pending/in-progress tasks.
7
+ *
8
+ * Configuration priority:
9
+ * 1. settings.json checkTasksConfig (project or global)
10
+ * 2. Environment variables (legacy)
11
+ *
12
+ * Config options:
13
+ * - taskListId: specific list to check, or undefined = check all lists
14
+ * - keywords: keywords that trigger the check (default: ["dev"])
15
+ * - enabled: enable/disable the hook
16
+ */
17
+
18
+ import { readdirSync, readFileSync, existsSync } from "fs";
19
+ import { join } from "path";
20
+ import { homedir } from "os";
21
+
22
+ interface Task {
23
+ id: string;
24
+ subject: string;
25
+ status: "pending" | "in_progress" | "completed";
26
+ }
27
+
28
+ interface CheckTasksConfig {
29
+ taskListId?: string;
30
+ keywords?: string[];
31
+ enabled?: boolean;
32
+ }
33
+
34
+ interface HookInput {
35
+ session_id: string;
36
+ transcript_path: string;
37
+ cwd: string;
38
+ permission_mode: string;
39
+ hook_event_name: string;
40
+ stop_hook_active: boolean;
41
+ }
42
+
43
+ const CONFIG_KEY = "checkTasksConfig";
44
+
45
+ function readStdinJson(): HookInput | null {
46
+ try {
47
+ const stdin = readFileSync(0, "utf-8");
48
+ return JSON.parse(stdin);
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ function readSettings(path: string): Record<string, unknown> {
55
+ if (!existsSync(path)) return {};
56
+ try {
57
+ return JSON.parse(readFileSync(path, "utf-8"));
58
+ } catch {
59
+ return {};
60
+ }
61
+ }
62
+
63
+ function getConfig(cwd: string): CheckTasksConfig {
64
+ // Try project settings first
65
+ const projectSettings = readSettings(join(cwd, ".claude", "settings.json"));
66
+ if (projectSettings[CONFIG_KEY]) {
67
+ return projectSettings[CONFIG_KEY] as CheckTasksConfig;
68
+ }
69
+
70
+ // Fall back to global settings
71
+ const globalSettings = readSettings(join(homedir(), ".claude", "settings.json"));
72
+ if (globalSettings[CONFIG_KEY]) {
73
+ return globalSettings[CONFIG_KEY] as CheckTasksConfig;
74
+ }
75
+
76
+ // Legacy: use environment variables
77
+ return {
78
+ taskListId: process.env.CLAUDE_CODE_TASK_LIST_ID,
79
+ keywords: process.env.CHECK_TASKS_KEYWORDS?.split(",").map((k) => k.trim().toLowerCase()).filter(Boolean) || ["dev"],
80
+ enabled: process.env.CHECK_TASKS_DISABLED !== "1",
81
+ };
82
+ }
83
+
84
+ function getSessionName(transcriptPath: string): string | null {
85
+ if (!existsSync(transcriptPath)) return null;
86
+
87
+ try {
88
+ const content = readFileSync(transcriptPath, "utf-8");
89
+ let lastTitle: string | null = null;
90
+ let searchStart = 0;
91
+
92
+ while (true) {
93
+ const titleIndex = content.indexOf('"custom-title"', searchStart);
94
+ if (titleIndex === -1) break;
95
+
96
+ const lineStart = content.lastIndexOf("\n", titleIndex) + 1;
97
+ const lineEnd = content.indexOf("\n", titleIndex);
98
+ const line = content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd);
99
+
100
+ try {
101
+ const entry = JSON.parse(line);
102
+ if (entry.type === "custom-title" && entry.customTitle) {
103
+ lastTitle = entry.customTitle;
104
+ }
105
+ } catch {
106
+ // Skip malformed lines
107
+ }
108
+
109
+ searchStart = titleIndex + 1;
110
+ }
111
+
112
+ return lastTitle;
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ function getAllTaskLists(): string[] {
119
+ const tasksDir = join(homedir(), ".claude", "tasks");
120
+ if (!existsSync(tasksDir)) return [];
121
+ try {
122
+ return readdirSync(tasksDir, { withFileTypes: true })
123
+ .filter((d) => d.isDirectory())
124
+ .map((d) => d.name);
125
+ } catch {
126
+ return [];
127
+ }
128
+ }
129
+
130
+ function getProjectTaskLists(cwd: string): string[] {
131
+ const allLists = getAllTaskLists();
132
+
133
+ // Get the directory name as project identifier
134
+ const dirName = cwd.split("/").filter(Boolean).pop() || "";
135
+
136
+ // Filter lists that match the project name
137
+ const projectLists = allLists.filter((list) => {
138
+ const listLower = list.toLowerCase();
139
+ const dirLower = dirName.toLowerCase();
140
+
141
+ // Exact prefix match (e.g., "connect-x" matches "connect-x-dev")
142
+ if (listLower.startsWith(dirLower + "-")) return true;
143
+
144
+ // Exact match
145
+ if (listLower === dirLower) return true;
146
+
147
+ return false;
148
+ });
149
+
150
+ return projectLists;
151
+ }
152
+
153
+ function getTasksFromList(listId: string): Task[] {
154
+ const tasksDir = join(homedir(), ".claude", "tasks", listId);
155
+ if (!existsSync(tasksDir)) return [];
156
+
157
+ try {
158
+ const taskFiles = readdirSync(tasksDir).filter((f) => f.endsWith(".json"));
159
+ return taskFiles.map((file) => {
160
+ const content = readFileSync(join(tasksDir, file), "utf-8");
161
+ return JSON.parse(content) as Task;
162
+ });
163
+ } catch {
164
+ return [];
165
+ }
166
+ }
167
+
168
+ function approve() {
169
+ console.log(JSON.stringify({ decision: "approve" }));
170
+ process.exit(0);
171
+ }
172
+
173
+ function block(reason: string) {
174
+ console.log(JSON.stringify({ decision: "block", reason }));
175
+ process.exit(0);
176
+ }
177
+
178
+ export function run() {
179
+ const hookInput = readStdinJson();
180
+ const cwd = hookInput?.cwd || process.cwd();
181
+
182
+ const config = getConfig(cwd);
183
+
184
+ // Check if hook is disabled
185
+ if (config.enabled === false) {
186
+ approve();
187
+ }
188
+
189
+ // Get session name from transcript
190
+ let sessionName: string | null = null;
191
+ if (hookInput?.transcript_path) {
192
+ sessionName = getSessionName(hookInput.transcript_path);
193
+ }
194
+
195
+ // Determine what to check for keywords
196
+ const nameToCheck = sessionName || config.taskListId || "";
197
+ const keywords = config.keywords || ["dev"];
198
+
199
+ // Only block stop for sessions matching configured keywords
200
+ const matchesKeyword = keywords.some((keyword) =>
201
+ nameToCheck.toLowerCase().includes(keyword.toLowerCase())
202
+ );
203
+
204
+ if (!matchesKeyword && keywords.length > 0 && nameToCheck) {
205
+ // Not a matching session, allow stop
206
+ approve();
207
+ }
208
+
209
+ // Determine which task lists to check
210
+ let listsToCheck: string[] = [];
211
+
212
+ if (config.taskListId) {
213
+ // Specific list configured
214
+ listsToCheck = [config.taskListId];
215
+ } else {
216
+ // Get lists that belong to this project/folder
217
+ const projectLists = getProjectTaskLists(cwd);
218
+
219
+ if (projectLists.length > 0) {
220
+ // Filter by keywords if specified
221
+ if (keywords.length > 0) {
222
+ listsToCheck = projectLists.filter((list) =>
223
+ keywords.some((keyword) => list.toLowerCase().includes(keyword.toLowerCase()))
224
+ );
225
+ } else {
226
+ listsToCheck = projectLists;
227
+ }
228
+ }
229
+ // If no project-specific lists found, don't check any (don't fall back to all lists)
230
+ }
231
+
232
+ if (listsToCheck.length === 0) {
233
+ // No matching task lists, allow stop
234
+ approve();
235
+ }
236
+
237
+ // Collect tasks from all matching lists
238
+ let allPending: Task[] = [];
239
+ let allInProgress: Task[] = [];
240
+ let allCompleted: Task[] = [];
241
+ let activeListId: string | null = null;
242
+
243
+ for (const listId of listsToCheck) {
244
+ const tasks = getTasksFromList(listId);
245
+ const pending = tasks.filter((t) => t.status === "pending");
246
+ const inProgress = tasks.filter((t) => t.status === "in_progress");
247
+ const completed = tasks.filter((t) => t.status === "completed");
248
+
249
+ if (pending.length > 0 || inProgress.length > 0) {
250
+ activeListId = listId;
251
+ }
252
+
253
+ allPending.push(...pending);
254
+ allInProgress.push(...inProgress);
255
+ allCompleted.push(...completed);
256
+ }
257
+
258
+ const remainingCount = allPending.length + allInProgress.length;
259
+
260
+ if (remainingCount > 0) {
261
+ const nextTasks = allPending
262
+ .slice(0, 3)
263
+ .map((t) => `- ${t.subject}`)
264
+ .join("\n");
265
+
266
+ const listInfo = activeListId ? ` in "${activeListId}"` : "";
267
+
268
+ const prompt = `
269
+ STOP BLOCKED: You have ${remainingCount} tasks remaining${listInfo} (${allPending.length} pending, ${allInProgress.length} in progress, ${allCompleted.length} completed).
270
+
271
+ ⛔ DO NOT STOP. DO NOT ASK QUESTIONS. DO NOT WAIT FOR USER INPUT.
272
+
273
+ You MUST continue working AUTONOMOUSLY until ALL tasks are completed.
274
+
275
+ Next pending tasks:
276
+ ${nextTasks}
277
+ ${allPending.length > 3 ? `... and ${allPending.length - 3} more pending tasks` : ""}
278
+
279
+ MANDATORY INSTRUCTIONS (follow these NOW):
280
+ 1. Use TaskList to see all tasks
281
+ 2. Use TaskGet to read the FIRST pending task's full description
282
+ 3. Use TaskUpdate to mark it as in_progress BEFORE starting work
283
+ 4. Complete the task (write code, run commands, etc.)
284
+ 5. Use TaskUpdate to mark it as completed AFTER finishing
285
+ 6. IMMEDIATELY move to the next task - DO NOT STOP
286
+
287
+ CRITICAL RULES:
288
+ - NEVER ask "would you like me to..." - just DO IT
289
+ - NEVER ask for confirmation - just WORK
290
+ - NEVER stop to explain what you'll do - just DO IT
291
+ - If a task is unclear, make reasonable assumptions and proceed
292
+ - If you encounter an error, fix it and continue
293
+ - Keep working until remainingCount = 0
294
+
295
+ START WORKING NOW. Use TaskList tool in your next response.
296
+ `.trim();
297
+
298
+ block(prompt);
299
+ }
300
+
301
+ // All tasks completed, allow stop
302
+ approve();
303
+ }
304
+
305
+ // Allow direct execution
306
+ if (import.meta.main) {
307
+ run();
308
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "resolveJsonModule": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "outDir": "./dist",
15
+ "rootDir": "./src",
16
+ "types": ["bun-types", "node"]
17
+ },
18
+ "include": ["src/**/*.ts"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
@@ -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,137 @@
1
+ # @hasnaxyz/hook-checktests
2
+
3
+ Claude Code hook that checks for missing tests via a headless Claude agent. Runs async (non-blocking) on PostToolUse after file edits.
4
+
5
+ ## Features
6
+
7
+ - **Edit tracking**: Monitors Edit, Write, NotebookEdit tools
8
+ - **Configurable threshold**: Run after N edits (3-7, default: 3)
9
+ - **Headless review**: Spawns Claude agent to analyze test coverage
10
+ - **Task dispatch**: Creates tasks via `service-implementation task dispatch`
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-checktests
20
+ hook-checktests install --global
21
+ ```
22
+
23
+ ### Project-specific
24
+
25
+ ```bash
26
+ cd /path/to/your/project
27
+ bunx @hasnaxyz/hook-checktests 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.
38
+
39
+ ### Commands
40
+
41
+ ```bash
42
+ hook-checktests install [path] # Install the hook
43
+ hook-checktests config [path] # Update configuration
44
+ hook-checktests uninstall [path] # Remove the hook
45
+ hook-checktests status # Show hook status
46
+ hook-checktests 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": { "tool_name": "^(Edit|Write|NotebookEdit)$" },
63
+ "hooks": [{
64
+ "type": "command",
65
+ "command": "bunx @hasnaxyz/hook-checktests@latest run",
66
+ "timeout": 120,
67
+ "async": true
68
+ }]
69
+ }]
70
+ },
71
+ "checkTestsConfig": {
72
+ "editThreshold": 3,
73
+ "taskListId": "myproject-qa",
74
+ "keywords": ["dev"],
75
+ "enabled": true
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### Options
81
+
82
+ | Option | Type | Default | Description |
83
+ |--------|------|---------|-------------|
84
+ | `editThreshold` | number | 3 | Run review after this many edits (3-7) |
85
+ | `taskListId` | string | auto | Task list for dispatching tasks (auto-detects `*-qa`) |
86
+ | `keywords` | string[] | ["dev"] | Only run for matching sessions |
87
+ | `enabled` | boolean | true | Enable/disable the hook |
88
+
89
+ ## How It Works
90
+
91
+ 1. **Tracks edits**: Monitors Edit, Write, NotebookEdit tool calls
92
+ 2. **Counts edits**: Increments counter for each unique file edited
93
+ 3. **Threshold check**: After N edits, spawns headless Claude agent
94
+ 4. **Test review**: Agent analyzes edited files for missing tests
95
+ 5. **Task dispatch**: Creates tasks via `service-implementation task dispatch`
96
+ 6. **Reset**: Counter resets after each review
97
+
98
+ ## Test Issues Detected
99
+
100
+ The hook checks for:
101
+
102
+ - Missing unit tests for new functions/methods
103
+ - Missing integration tests for new features
104
+ - Missing edge case tests
105
+ - Missing error handling tests
106
+ - Untested code paths
107
+ - Missing mock/stub implementations
108
+ - Missing test fixtures or setup
109
+ - Missing API endpoint tests
110
+ - Missing validation tests
111
+
112
+ ## Task Format
113
+
114
+ Tasks are dispatched with:
115
+
116
+ ```bash
117
+ service-implementation task dispatch "myproject-qa" \
118
+ -s "TEST: [brief description]" \
119
+ -d "[detailed description of what tests need to be added]"
120
+ ```
121
+
122
+ ## Session State
123
+
124
+ State is persisted in `~/.claude/hook-state/checktests-{session_id}.json`:
125
+
126
+ ```json
127
+ {
128
+ "editCount": 2,
129
+ "editedFiles": ["src/utils.ts", "src/api.ts"],
130
+ "lastCheckRun": 1706500000000,
131
+ "checkInProgress": false
132
+ }
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@hasnaxyz/hook-checktests",
3
+ "version": "0.1.6",
4
+ "description": "Claude Code hook that checks for missing tests and creates tasks",
5
+ "type": "module",
6
+ "bin": {
7
+ "hook-checktests": "./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
+ "tests",
33
+ "testing",
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-checktests.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
+ }