@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,334 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * Claude Code Hook: check-tests
5
- *
6
- * Runs a headless Claude Code agent to check for missing tests.
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 test 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 CheckTestsConfig {
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 = "checkTestsConfig";
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
- return id.replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 100) || 'default';
57
- }
58
-
59
- /**
60
- * Sanitize file path for safe display in prompts
61
- */
62
- function sanitizePath(path: string): string {
63
- if (!path || typeof path !== 'string') return '';
64
- return path.replace(/[`$"'\\;&|<>(){}[\]!#*?~]/g, '_');
65
- }
66
-
67
- const TESTS_PROMPT = `You are a test coverage reviewer. Review the following files that were recently edited and identify missing or incomplete tests:
68
-
69
- FILES TO REVIEW:
70
- {files}
71
-
72
- For each test coverage issue found, create a task using the service-implementation CLI:
73
- service-implementation task dispatch "{taskListId}" -s "TEST: [brief description]" -d "[detailed description of what tests need to be added]"
74
-
75
- Focus on:
76
- - Missing unit tests for new functions/methods
77
- - Missing integration tests for new features
78
- - Missing edge case tests
79
- - Missing error handling tests
80
- - Untested code paths
81
- - Missing mock/stub implementations
82
- - Missing test fixtures or setup
83
- - Missing API endpoint tests
84
- - Missing validation tests
85
-
86
- If no test coverage issues are found, do not create any tasks.
87
- Only create tasks for meaningful test gaps, not trivial ones.
88
- Limit to max 5 most important test tasks.`;
89
-
90
- function isValidRepoPattern(cwd: string): boolean {
91
- const dirName = cwd.split("/").filter(Boolean).pop() || "";
92
- // Match: hook-checklint, skill-installhook, iapp-mail, etc.
93
- return /^[a-z]+-[a-z0-9-]+$/i.test(dirName);
94
- }
95
-
96
- function readStdinJson(): HookInput | null {
97
- try {
98
- const stdin = readFileSync(0, "utf-8");
99
- return JSON.parse(stdin);
100
- } catch {
101
- return null;
102
- }
103
- }
104
-
105
- function readSettings(path: string): Record<string, unknown> {
106
- if (!existsSync(path)) return {};
107
- try {
108
- return JSON.parse(readFileSync(path, "utf-8"));
109
- } catch {
110
- return {};
111
- }
112
- }
113
-
114
- function getConfig(cwd: string): CheckTestsConfig {
115
- // Try project settings first
116
- const projectSettings = readSettings(join(cwd, ".claude", "settings.json"));
117
- if (projectSettings[CONFIG_KEY]) {
118
- return projectSettings[CONFIG_KEY] as CheckTestsConfig;
119
- }
120
-
121
- // Fall back to global settings
122
- const globalSettings = readSettings(join(homedir(), ".claude", "settings.json"));
123
- if (globalSettings[CONFIG_KEY]) {
124
- return globalSettings[CONFIG_KEY] as CheckTestsConfig;
125
- }
126
-
127
- // Default config
128
- return {
129
- editThreshold: 3,
130
- keywords: ["dev"],
131
- enabled: true,
132
- };
133
- }
134
-
135
- function getStateFile(sessionId: string): string {
136
- mkdirSync(STATE_DIR, { recursive: true });
137
- const safeSessionId = sanitizeId(sessionId);
138
- return join(STATE_DIR, `checktests-${safeSessionId}.json`);
139
- }
140
-
141
- function getSessionState(sessionId: string): SessionState {
142
- const stateFile = getStateFile(sessionId);
143
- if (existsSync(stateFile)) {
144
- try {
145
- return JSON.parse(readFileSync(stateFile, "utf-8"));
146
- } catch {
147
- // Corrupted state, reset
148
- }
149
- }
150
- return { editCount: 0, editedFiles: [], lastCheckRun: 0, checkInProgress: false };
151
- }
152
-
153
- function saveSessionState(sessionId: string, state: SessionState): void {
154
- const stateFile = getStateFile(sessionId);
155
- writeFileSync(stateFile, JSON.stringify(state, null, 2));
156
- }
157
-
158
- function getSessionName(transcriptPath: string): string | null {
159
- if (!existsSync(transcriptPath)) return null;
160
-
161
- try {
162
- const content = readFileSync(transcriptPath, "utf-8");
163
- let lastTitle: string | null = null;
164
- let searchStart = 0;
165
-
166
- while (true) {
167
- const titleIndex = content.indexOf('"custom-title"', searchStart);
168
- if (titleIndex === -1) break;
169
-
170
- const lineStart = content.lastIndexOf("\n", titleIndex) + 1;
171
- const lineEnd = content.indexOf("\n", titleIndex);
172
- const line = content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd);
173
-
174
- try {
175
- const entry = JSON.parse(line);
176
- if (entry.type === "custom-title" && entry.customTitle) {
177
- lastTitle = entry.customTitle;
178
- }
179
- } catch {
180
- // Skip malformed lines
181
- }
182
-
183
- searchStart = titleIndex + 1;
184
- }
185
-
186
- return lastTitle;
187
- } catch {
188
- return null;
189
- }
190
- }
191
-
192
- function getProjectTaskListId(cwd: string): string | null {
193
- const dirName = cwd.split("/").filter(Boolean).pop() || "";
194
- return `${dirName}-qa`;
195
- }
196
-
197
- function runHeadlessTestsCheck(
198
- cwd: string,
199
- files: string[],
200
- taskListId: string
201
- ): void {
202
- // Sanitize file paths to prevent prompt injection
203
- const filesFormatted = files.map((f) => `- ${sanitizePath(f)}`).join("\n");
204
- // Sanitize taskListId
205
- const safeTaskListId = sanitizeId(taskListId);
206
-
207
- const prompt = TESTS_PROMPT
208
- .replace("{files}", filesFormatted)
209
- .replace("{taskListId}", safeTaskListId);
210
-
211
- // Spawn headless Claude Code agent in background
212
- const child = spawn(
213
- "claude",
214
- [
215
- "-p",
216
- prompt,
217
- "--permission-mode",
218
- "acceptEdits",
219
- "--allowedTools",
220
- "Bash,Read",
221
- "--no-session-persistence",
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-checktests] Started tests 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
- if (keywords.length > 0 && nameToCheck && !matchesKeyword) {
281
- approve();
282
- return;
283
- }
284
-
285
- // Get edited file path
286
- const filePath = (tool_input.file_path || tool_input.notebook_path) as string | undefined;
287
- if (!filePath) {
288
- approve();
289
- return;
290
- }
291
-
292
- // Update session state
293
- const state = getSessionState(session_id);
294
-
295
- // Skip if check already in progress
296
- if (state.checkInProgress) {
297
- approve();
298
- return;
299
- }
300
-
301
- state.editCount++;
302
-
303
- if (!state.editedFiles.includes(filePath)) {
304
- state.editedFiles.push(filePath);
305
- }
306
-
307
- const threshold = Math.min(7, Math.max(3, config.editThreshold || 3));
308
-
309
- // Check if we should run tests check
310
- if (state.editCount >= threshold) {
311
- const taskListId = config.taskListId || getProjectTaskListId(cwd) || "default-qa";
312
-
313
- // Mark check in progress
314
- state.checkInProgress = true;
315
- saveSessionState(session_id, state);
316
-
317
- // Run headless tests check (async, non-blocking)
318
- runHeadlessTestsCheck(cwd, state.editedFiles, taskListId);
319
-
320
- // Reset counter after starting check
321
- state.editCount = 0;
322
- state.editedFiles = [];
323
- state.lastCheckRun = Date.now();
324
- state.checkInProgress = false;
325
- }
326
-
327
- saveSessionState(session_id, state);
328
- approve();
329
- }
330
-
331
- // Allow direct execution
332
- if (import.meta.main) {
333
- run();
334
- }
@@ -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
- }
@@ -1,152 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * CLI for hook-contextrefresh
5
- */
6
-
7
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
8
- import { join } from "path";
9
- import { homedir } from "os";
10
-
11
- const HOOK_NAME = "hook-contextrefresh";
12
- const SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
13
-
14
- interface ClaudeSettings {
15
- hooks?: {
16
- UserPromptSubmit?: Array<{
17
- matcher?: string;
18
- hooks: Array<{ type: "command"; command: string }>;
19
- }>;
20
- };
21
- contextRefreshConfig?: {
22
- enabled?: boolean;
23
- interval?: number;
24
- contextFile?: string;
25
- };
26
- [key: string]: unknown;
27
- }
28
-
29
- function readSettings(): ClaudeSettings {
30
- try {
31
- if (existsSync(SETTINGS_PATH)) {
32
- return JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
33
- }
34
- } catch {}
35
- return {};
36
- }
37
-
38
- function writeSettings(settings: ClaudeSettings): void {
39
- const dir = join(homedir(), ".claude");
40
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
41
- writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
42
- }
43
-
44
- function install(interval?: string): void {
45
- const settings = readSettings();
46
- if (!settings.hooks) settings.hooks = {};
47
- if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
48
-
49
- const existing = settings.hooks.UserPromptSubmit.find((h) =>
50
- h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
51
- );
52
-
53
- if (existing) {
54
- console.log(`${HOOK_NAME} is already installed`);
55
- return;
56
- }
57
-
58
- settings.hooks.UserPromptSubmit.push({
59
- hooks: [{ type: "command", command: `bunx @hasnaxyz/${HOOK_NAME}` }],
60
- });
61
-
62
- if (!settings.contextRefreshConfig) {
63
- settings.contextRefreshConfig = {
64
- enabled: true,
65
- interval: interval ? parseInt(interval, 10) : 10,
66
- };
67
- }
68
-
69
- writeSettings(settings);
70
- console.log(`${HOOK_NAME} installed successfully`);
71
- console.log(`Interval: every ${settings.contextRefreshConfig.interval} prompts`);
72
- console.log(`\nCreate a .claude-context file in your project root with the context to inject.`);
73
- }
74
-
75
- function uninstall(): void {
76
- const settings = readSettings();
77
- if (!settings.hooks?.UserPromptSubmit) {
78
- console.log(`${HOOK_NAME} is not installed`);
79
- return;
80
- }
81
-
82
- const before = settings.hooks.UserPromptSubmit.length;
83
- settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
84
- (h) => !h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
85
- );
86
-
87
- if (before === settings.hooks.UserPromptSubmit.length) {
88
- console.log(`${HOOK_NAME} is not installed`);
89
- return;
90
- }
91
-
92
- writeSettings(settings);
93
- console.log(`${HOOK_NAME} uninstalled successfully`);
94
- }
95
-
96
- function status(): void {
97
- const settings = readSettings();
98
- const installed = settings.hooks?.UserPromptSubmit?.some((h) =>
99
- h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
100
- );
101
- console.log(`${HOOK_NAME} is ${installed ? "installed" : "not installed"}`);
102
-
103
- if (settings.contextRefreshConfig) {
104
- console.log(`\nConfig:`);
105
- console.log(` Enabled: ${settings.contextRefreshConfig.enabled !== false}`);
106
- console.log(` Interval: every ${settings.contextRefreshConfig.interval || 10} prompts`);
107
- console.log(` Context file: ${settings.contextRefreshConfig.contextFile || ".claude-context"}`);
108
- }
109
-
110
- // Check if context file exists
111
- const contextFile = join(process.cwd(), ".claude-context");
112
- console.log(`\nContext file: ${existsSync(contextFile) ? "found" : "not found"} (${contextFile})`);
113
- }
114
-
115
- function help(): void {
116
- console.log(`
117
- ${HOOK_NAME} - Re-inject context every N prompts
118
-
119
- Usage: ${HOOK_NAME} <command>
120
-
121
- Commands:
122
- install [N] Install hook (optional: set interval, default 10)
123
- uninstall Remove hook from Claude Code settings
124
- status Check if hook is installed and show config
125
- help Show this help message
126
-
127
- Setup:
128
- 1. Run: ${HOOK_NAME} install
129
- 2. Create .claude-context in your project root
130
- 3. Add important context/rules to that file
131
- 4. Context is re-injected every N prompts automatically
132
- `);
133
- }
134
-
135
- const command = process.argv[2];
136
-
137
- switch (command) {
138
- case "install": install(process.argv[3]); break;
139
- case "uninstall": uninstall(); break;
140
- case "status": status(); break;
141
- case "help":
142
- case "--help":
143
- case "-h": help(); break;
144
- default:
145
- if (!command) {
146
- import("./hook.ts").then((m) => m.run());
147
- } else {
148
- console.error(`Unknown command: ${command}`);
149
- help();
150
- process.exit(1);
151
- }
152
- }
@@ -1,148 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * Claude Code Hook: contextrefresh
5
- *
6
- * UserPromptSubmit hook that re-injects important context every N prompts
7
- * to prevent context decay in long sessions.
8
- *
9
- * Reads context from .claude-context file in project root and injects
10
- * it as a system message every N user prompts. Tracks prompt count
11
- * in a persistent counter file.
12
- */
13
-
14
- import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs";
15
- import { join } from "path";
16
- import { homedir } from "os";
17
- import { tmpdir } from "os";
18
-
19
- interface HookInput {
20
- session_id: string;
21
- cwd: string;
22
- hook_event_name: string;
23
- user_prompt?: string;
24
- }
25
-
26
- interface HookOutput {
27
- continue?: boolean;
28
- suppressPrompt?: boolean;
29
- updatedPrompt?: string;
30
- }
31
-
32
- interface RefreshConfig {
33
- enabled?: boolean;
34
- interval?: number;
35
- contextFile?: string;
36
- }
37
-
38
- const CONFIG_KEY = "contextRefreshConfig";
39
- const COUNTER_DIR = join(tmpdir(), "hook-contextrefresh");
40
-
41
- function readStdinJson(): HookInput | null {
42
- try {
43
- const input = readFileSync(0, "utf-8").trim();
44
- if (!input) return null;
45
- return JSON.parse(input);
46
- } catch {
47
- return null;
48
- }
49
- }
50
-
51
- function getConfig(): RefreshConfig {
52
- const settingsPath = join(homedir(), ".claude", "settings.json");
53
- try {
54
- if (existsSync(settingsPath)) {
55
- const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
56
- return settings[CONFIG_KEY] || { enabled: true, interval: 10 };
57
- }
58
- } catch {}
59
- return { enabled: true, interval: 10 };
60
- }
61
-
62
- function getPromptCount(sessionId: string): number {
63
- mkdirSync(COUNTER_DIR, { recursive: true });
64
- const counterFile = join(COUNTER_DIR, `${sessionId}.count`);
65
- try {
66
- if (existsSync(counterFile)) {
67
- return parseInt(readFileSync(counterFile, "utf-8").trim(), 10) || 0;
68
- }
69
- } catch {}
70
- return 0;
71
- }
72
-
73
- function setPromptCount(sessionId: string, count: number): void {
74
- mkdirSync(COUNTER_DIR, { recursive: true });
75
- const counterFile = join(COUNTER_DIR, `${sessionId}.count`);
76
- writeFileSync(counterFile, String(count));
77
- }
78
-
79
- function getContextContent(cwd: string, contextFile?: string): string | null {
80
- // Try configured context file, then defaults
81
- const candidates = [
82
- contextFile ? join(cwd, contextFile) : null,
83
- join(cwd, ".claude-context"),
84
- join(cwd, ".claude-refresh"),
85
- ].filter(Boolean) as string[];
86
-
87
- for (const path of candidates) {
88
- if (existsSync(path)) {
89
- try {
90
- return readFileSync(path, "utf-8").trim();
91
- } catch {}
92
- }
93
- }
94
-
95
- return null;
96
- }
97
-
98
- function respond(output: HookOutput): void {
99
- console.log(JSON.stringify(output));
100
- }
101
-
102
- export function run(): void {
103
- const input = readStdinJson();
104
-
105
- if (!input) {
106
- respond({ continue: true });
107
- return;
108
- }
109
-
110
- const config = getConfig();
111
-
112
- if (!config.enabled) {
113
- respond({ continue: true });
114
- return;
115
- }
116
-
117
- const interval = config.interval || 10;
118
- const count = getPromptCount(input.session_id) + 1;
119
- setPromptCount(input.session_id, count);
120
-
121
- // Check if it's time to inject context
122
- if (count % interval !== 0) {
123
- respond({ continue: true });
124
- return;
125
- }
126
-
127
- const contextContent = getContextContent(input.cwd, config.contextFile);
128
-
129
- if (!contextContent) {
130
- respond({ continue: true });
131
- return;
132
- }
133
-
134
- // Inject context by prepending it to the user's prompt
135
- const refreshPrefix = `[Context Refresh - Prompt #${count}]\n${contextContent}\n\n---\n\n`;
136
- const updatedPrompt = input.user_prompt
137
- ? `${refreshPrefix}${input.user_prompt}`
138
- : undefined;
139
-
140
- respond({
141
- continue: true,
142
- updatedPrompt,
143
- });
144
- }
145
-
146
- if (import.meta.main) {
147
- run();
148
- }
@@ -1,25 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "lib": ["ESNext"],
6
- "moduleResolution": "bundler",
7
- "allowImportingTsExtensions": true,
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
14
- "noEmit": true,
15
- "noUnusedLocals": true,
16
- "noUnusedParameters": true,
17
- "declaration": true,
18
- "declarationMap": true,
19
- "outDir": "./dist",
20
- "rootDir": "./src",
21
- "types": ["bun-types"]
22
- },
23
- "include": ["src/**/*"],
24
- "exclude": ["node_modules", "dist"]
25
- }