@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,321 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Claude Code Hook: check-files
5
+ *
6
+ * Runs a headless Claude Code agent to review files and create tasks.
7
+ * Uses service-implementation CLI to dispatch tasks.
8
+ *
9
+ * This hook runs ASYNC (non-blocking) on PostToolUse.
10
+ *
11
+ * Configuration:
12
+ * - taskListId: task list for dispatching review tasks
13
+ * - editThreshold: run review after this many edits (default: 3, range: 3-7)
14
+ * - keywords: keywords that trigger the check (default: ["dev"])
15
+ * - reviewPrompt: custom prompt for the headless agent
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 CheckFilesConfig {
25
+ taskListId?: string;
26
+ editThreshold?: number;
27
+ keywords?: string[];
28
+ reviewPrompt?: string;
29
+ enabled?: boolean;
30
+ }
31
+
32
+ interface HookInput {
33
+ session_id: string;
34
+ transcript_path: string;
35
+ cwd: string;
36
+ tool_name: string;
37
+ tool_input: Record<string, unknown>;
38
+ tool_output?: string;
39
+ }
40
+
41
+ interface SessionState {
42
+ editCount: number;
43
+ editedFiles: string[];
44
+ lastReviewRun: number;
45
+ reviewInProgress: boolean;
46
+ }
47
+
48
+ const CONFIG_KEY = "checkFilesConfig";
49
+ const STATE_DIR = join(homedir(), ".claude", "hook-state");
50
+ const EDIT_TOOLS = ["Edit", "Write", "NotebookEdit"];
51
+
52
+ /**
53
+ * Sanitize ID to prevent path traversal and injection attacks
54
+ */
55
+ function sanitizeId(id: string): string {
56
+ if (!id || typeof id !== 'string') return 'default';
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
+ return path.replace(/[`$"'\\;&|<>(){}[\]!#*?~]/g, '_');
66
+ }
67
+
68
+ const DEFAULT_REVIEW_PROMPT = `You are a code reviewer. Review the following files that were recently edited and identify any issues:
69
+
70
+ FILES TO REVIEW:
71
+ {files}
72
+
73
+ For each issue found, create a task using the service-implementation CLI:
74
+ service-implementation task dispatch "{taskListId}" -s "REVIEW: [brief issue description]" -d "[detailed description with file:line reference]"
75
+
76
+ Focus on:
77
+ - Potential bugs or logic errors
78
+ - Security vulnerabilities
79
+ - Performance issues
80
+ - Code style violations
81
+ - Missing error handling
82
+
83
+ If no issues are found, do not create any tasks.
84
+ Only create tasks for real issues, not minor style preferences.
85
+ Limit to max 5 most important issues.`;
86
+
87
+ function readStdinJson(): HookInput | null {
88
+ try {
89
+ const stdin = readFileSync(0, "utf-8");
90
+ return JSON.parse(stdin);
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+
96
+ function readSettings(path: string): Record<string, unknown> {
97
+ if (!existsSync(path)) return {};
98
+ try {
99
+ return JSON.parse(readFileSync(path, "utf-8"));
100
+ } catch {
101
+ return {};
102
+ }
103
+ }
104
+
105
+ function getConfig(cwd: string): CheckFilesConfig {
106
+ // Try project settings first
107
+ const projectSettings = readSettings(join(cwd, ".claude", "settings.json"));
108
+ if (projectSettings[CONFIG_KEY]) {
109
+ return projectSettings[CONFIG_KEY] as CheckFilesConfig;
110
+ }
111
+
112
+ // Fall back to global settings
113
+ const globalSettings = readSettings(join(homedir(), ".claude", "settings.json"));
114
+ if (globalSettings[CONFIG_KEY]) {
115
+ return globalSettings[CONFIG_KEY] as CheckFilesConfig;
116
+ }
117
+
118
+ // Default config
119
+ return {
120
+ editThreshold: 3,
121
+ keywords: ["dev"],
122
+ enabled: true,
123
+ };
124
+ }
125
+
126
+ function getStateFile(sessionId: string): string {
127
+ mkdirSync(STATE_DIR, { recursive: true });
128
+ const safeSessionId = sanitizeId(sessionId);
129
+ return join(STATE_DIR, `checkfiles-${safeSessionId}.json`);
130
+ }
131
+
132
+ function getSessionState(sessionId: string): SessionState {
133
+ const stateFile = getStateFile(sessionId);
134
+ if (existsSync(stateFile)) {
135
+ try {
136
+ return JSON.parse(readFileSync(stateFile, "utf-8"));
137
+ } catch {
138
+ // Corrupted state, reset
139
+ }
140
+ }
141
+ return { editCount: 0, editedFiles: [], lastReviewRun: 0, reviewInProgress: false };
142
+ }
143
+
144
+ function saveSessionState(sessionId: string, state: SessionState): void {
145
+ const stateFile = getStateFile(sessionId);
146
+ writeFileSync(stateFile, JSON.stringify(state, null, 2));
147
+ }
148
+
149
+ function getSessionName(transcriptPath: string): string | null {
150
+ if (!existsSync(transcriptPath)) return null;
151
+
152
+ try {
153
+ const content = readFileSync(transcriptPath, "utf-8");
154
+ let lastTitle: string | null = null;
155
+ let searchStart = 0;
156
+
157
+ while (true) {
158
+ const titleIndex = content.indexOf('"custom-title"', searchStart);
159
+ if (titleIndex === -1) break;
160
+
161
+ const lineStart = content.lastIndexOf("\n", titleIndex) + 1;
162
+ const lineEnd = content.indexOf("\n", titleIndex);
163
+ const line = content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd);
164
+
165
+ try {
166
+ const entry = JSON.parse(line);
167
+ if (entry.type === "custom-title" && entry.customTitle) {
168
+ lastTitle = entry.customTitle;
169
+ }
170
+ } catch {
171
+ // Skip malformed lines
172
+ }
173
+
174
+ searchStart = titleIndex + 1;
175
+ }
176
+
177
+ return lastTitle;
178
+ } catch {
179
+ return null;
180
+ }
181
+ }
182
+
183
+ function getProjectTaskListId(cwd: string): string | null {
184
+ const dirName = cwd.split("/").filter(Boolean).pop() || "";
185
+ // Default to project-bugfixes or project-dev
186
+ return `${dirName}-bugfixes`;
187
+ }
188
+
189
+ function runHeadlessReview(
190
+ cwd: string,
191
+ files: string[],
192
+ taskListId: string,
193
+ customPrompt?: string
194
+ ): void {
195
+ // Sanitize file paths to prevent prompt injection
196
+ const filesFormatted = files.map((f) => `- ${sanitizePath(f)}`).join("\n");
197
+ // Sanitize taskListId
198
+ const safeTaskListId = sanitizeId(taskListId);
199
+
200
+ const prompt = (customPrompt || DEFAULT_REVIEW_PROMPT)
201
+ .replace("{files}", filesFormatted)
202
+ .replace("{taskListId}", safeTaskListId);
203
+
204
+ // Spawn headless Claude Code agent in background
205
+ const child = spawn(
206
+ "claude",
207
+ [
208
+ "-p",
209
+ prompt,
210
+ "--permission-mode",
211
+ "acceptEdits",
212
+ "--allowedTools",
213
+ "Bash,Read",
214
+ "--no-session-persistence",
215
+ ],
216
+ {
217
+ cwd,
218
+ detached: true,
219
+ stdio: "ignore",
220
+ }
221
+ );
222
+
223
+ // Detach from parent process
224
+ child.unref();
225
+
226
+ console.error(`[hook-checkfiles] Started headless review of ${files.length} files`);
227
+ }
228
+
229
+ function approve() {
230
+ console.log(JSON.stringify({ decision: "approve" }));
231
+ process.exit(0);
232
+ }
233
+
234
+ export function run() {
235
+ const hookInput = readStdinJson();
236
+ if (!hookInput) {
237
+ approve();
238
+ return;
239
+ }
240
+
241
+ const { session_id, cwd, tool_name, tool_input, transcript_path } = hookInput;
242
+
243
+ // Only process edit tools
244
+ if (!EDIT_TOOLS.includes(tool_name)) {
245
+ approve();
246
+ return;
247
+ }
248
+
249
+ const config = getConfig(cwd);
250
+
251
+ // Check if hook is disabled
252
+ if (config.enabled === false) {
253
+ approve();
254
+ return;
255
+ }
256
+
257
+ // Check keywords match
258
+ const sessionName = transcript_path ? getSessionName(transcript_path) : null;
259
+ const nameToCheck = sessionName || config.taskListId || "";
260
+ const keywords = config.keywords || ["dev"];
261
+
262
+ const matchesKeyword = keywords.some((keyword) =>
263
+ nameToCheck.toLowerCase().includes(keyword.toLowerCase())
264
+ );
265
+
266
+ // If keywords are configured and we have a session name, check for match
267
+ if (keywords.length > 0 && nameToCheck && !matchesKeyword) {
268
+ approve();
269
+ return;
270
+ }
271
+
272
+ // Get edited file path
273
+ const filePath = (tool_input.file_path || tool_input.notebook_path) as string | undefined;
274
+ if (!filePath) {
275
+ approve();
276
+ return;
277
+ }
278
+
279
+ // Update session state
280
+ const state = getSessionState(session_id);
281
+
282
+ // Skip if review already in progress
283
+ if (state.reviewInProgress) {
284
+ approve();
285
+ return;
286
+ }
287
+
288
+ state.editCount++;
289
+
290
+ if (!state.editedFiles.includes(filePath)) {
291
+ state.editedFiles.push(filePath);
292
+ }
293
+
294
+ const threshold = Math.min(7, Math.max(3, config.editThreshold || 3));
295
+
296
+ // Check if we should run review
297
+ if (state.editCount >= threshold) {
298
+ const taskListId = config.taskListId || getProjectTaskListId(cwd) || "default-bugfixes";
299
+
300
+ // Mark review in progress
301
+ state.reviewInProgress = true;
302
+ saveSessionState(session_id, state);
303
+
304
+ // Run headless review (async, non-blocking)
305
+ runHeadlessReview(cwd, state.editedFiles, taskListId, config.reviewPrompt);
306
+
307
+ // Reset counter after starting review
308
+ state.editCount = 0;
309
+ state.editedFiles = [];
310
+ state.lastReviewRun = Date.now();
311
+ state.reviewInProgress = false;
312
+ }
313
+
314
+ saveSessionState(session_id, state);
315
+ approve();
316
+ }
317
+
318
+ // Allow direct execution
319
+ if (import.meta.main) {
320
+ run();
321
+ }
@@ -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,147 @@
1
+ # @hasnaxyz/hook-checklint
2
+
3
+ Claude Code hook that runs linting after file edits and creates tasks for errors.
4
+
5
+ ## Features
6
+
7
+ - **Automatic lint checking**: Runs lint after every N file edits (configurable, default: 3)
8
+ - **Task creation**: Creates bug tasks for lint errors so AI can fix them
9
+ - **Auto-detection**: Detects lint command and task lists automatically
10
+ - **Session-aware**: Only runs for sessions matching configured keywords
11
+ - **Multiple linters**: Supports ESLint, Biome, and custom lint commands
12
+
13
+ ## Installation
14
+
15
+ ### Global CLI
16
+
17
+ ```bash
18
+ bun add -g @hasnaxyz/hook-checklint
19
+ hook-checklint install --global
20
+ ```
21
+
22
+ ### Project-specific
23
+
24
+ ```bash
25
+ cd /path/to/your/project
26
+ bunx @hasnaxyz/hook-checklint install
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Once installed, the hook runs automatically after file edits (Edit, Write, NotebookEdit tools).
32
+
33
+ ### Commands
34
+
35
+ ```bash
36
+ hook-checklint install [path] # Install the hook
37
+ hook-checklint config [path] # Update configuration
38
+ hook-checklint uninstall [path] # Remove the hook
39
+ hook-checklint status # Show hook status
40
+ hook-checklint run # Execute hook (called by Claude Code)
41
+ ```
42
+
43
+ ### Options
44
+
45
+ - `--global`, `-g`: Apply to global settings (`~/.claude/settings.json`)
46
+ - `/path/to/repo`: Apply to specific project path
47
+
48
+ ## Configuration
49
+
50
+ Configuration is stored in `.claude/settings.json`:
51
+
52
+ ```json
53
+ {
54
+ "hooks": {
55
+ "PostToolUse": [{
56
+ "matcher": {
57
+ "tool_name": "^(Edit|Write|NotebookEdit)$"
58
+ },
59
+ "hooks": [{
60
+ "type": "command",
61
+ "command": "bunx @hasnaxyz/hook-checklint@latest run",
62
+ "timeout": 120
63
+ }]
64
+ }]
65
+ },
66
+ "checkLintConfig": {
67
+ "editThreshold": 3,
68
+ "lintCommand": "bun lint",
69
+ "taskListId": "myproject-bugfixes",
70
+ "keywords": ["dev"],
71
+ "createTasks": true,
72
+ "enabled": true
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### Options
78
+
79
+ | Option | Type | Default | Description |
80
+ |--------|------|---------|-------------|
81
+ | `editThreshold` | number | 3 | Run lint after this many edits (3-7) |
82
+ | `lintCommand` | string | auto | Lint command to run |
83
+ | `taskListId` | string | auto | Task list for error tasks |
84
+ | `keywords` | string[] | ["dev"] | Only run for matching sessions |
85
+ | `createTasks` | boolean | true | Create tasks for lint errors |
86
+ | `enabled` | boolean | true | Enable/disable the hook |
87
+
88
+ ## How It Works
89
+
90
+ 1. **Tracks file edits**: Monitors Edit, Write, and NotebookEdit tool calls
91
+ 2. **Counts edits**: Maintains a per-session edit counter
92
+ 3. **Triggers lint**: After N edits, runs the configured lint command
93
+ 4. **Parses output**: Extracts errors from ESLint/Biome output
94
+ 5. **Creates tasks**: For each error, creates a bug task in the configured task list
95
+ 6. **Resets counter**: After lint run, resets the edit counter
96
+
97
+ ## Auto-Detection
98
+
99
+ ### Lint Command
100
+
101
+ The hook auto-detects the lint command by checking:
102
+
103
+ 1. `package.json` scripts: `lint`, `lint:check`, `eslint`, `biome`
104
+ 2. Config files: `biome.json`, `.eslintrc.json`, `eslint.config.js`
105
+
106
+ ### Task List
107
+
108
+ The hook auto-detects the task list by:
109
+
110
+ 1. Looking for `{project}-bugfixes` list
111
+ 2. Falling back to `{project}-dev` list
112
+
113
+ ## Task Format
114
+
115
+ Created tasks follow this format:
116
+
117
+ ```
118
+ Subject: BUG: MEDIUM - Fix lint error in src/file.ts:42
119
+ Description:
120
+ Fix lint error at src/file.ts:42:5
121
+
122
+ **Error:** Unexpected console statement
123
+ **Rule:** no-console
124
+ **File:** src/file.ts
125
+ **Line:** 42
126
+ **Column:** 5
127
+
128
+ **Acceptance criteria:**
129
+ - Lint error is fixed
130
+ - No new lint errors introduced
131
+ ```
132
+
133
+ ## Session State
134
+
135
+ The hook maintains session state in `~/.claude/hook-state/checklint-{session_id}.json`:
136
+
137
+ ```json
138
+ {
139
+ "editCount": 2,
140
+ "editedFiles": ["src/file.ts", "src/other.ts"],
141
+ "lastLintRun": 1706500000000
142
+ }
143
+ ```
144
+
145
+ ## License
146
+
147
+ MIT
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@hasnaxyz/hook-checklint",
3
+ "version": "0.1.7",
4
+ "description": "Claude Code hook that runs linting after file edits and creates tasks for errors",
5
+ "type": "module",
6
+ "bin": {
7
+ "hook-checklint": "./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
+ "lint",
33
+ "eslint",
34
+ "biome",
35
+ "automation",
36
+ "cli"
37
+ ],
38
+ "author": "Hasna",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/hasnaxyz/hook-checklint.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
+ }
@@ -0,0 +1,32 @@
1
+ interface InstallOptions {
2
+ global?: boolean;
3
+ taskListId?: string;
4
+ keywords?: string[];
5
+ editThreshold?: number;
6
+ yes?: boolean;
7
+ path?: string;
8
+ }
9
+
10
+ function parseInstallArgs(args: string[]): InstallOptions {
11
+ const options: InstallOptions = {};
12
+
13
+ for (let i = 0; i < args.length; i++) {
14
+ const arg = args[i];
15
+
16
+ if (arg === "--global" || arg === "-g") {
17
+ options.global = true;
18
+ } else if (arg === "--yes" || arg === "-y") {
19
+ options.yes = true;
20
+ } else if (arg === "--task-list-id" || arg === "-t") {
21
+ options.taskListId = args[++i];
22
+ } else if (arg === "--keywords" || arg === "-k") {
23
+ options.keywords = args[++i]?.split(",").map(k => k.trim().toLowerCase()).filter(Boolean);
24
+ } else if (arg === "--threshold" || arg === "-n") {
25
+ options.editThreshold = parseInt(args[++i], 10);
26
+ } else if (!arg.startsWith("-")) {
27
+ options.path = arg;
28
+ }
29
+ }
30
+
31
+ return options;
32
+ }