wave-agent-sdk 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 (170) hide show
  1. package/README.md +32 -0
  2. package/dist/agent.d.ts +96 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +286 -0
  5. package/dist/hooks/executor.d.ts +56 -0
  6. package/dist/hooks/executor.d.ts.map +1 -0
  7. package/dist/hooks/executor.js +312 -0
  8. package/dist/hooks/index.d.ts +17 -0
  9. package/dist/hooks/index.d.ts.map +1 -0
  10. package/dist/hooks/index.js +14 -0
  11. package/dist/hooks/manager.d.ts +90 -0
  12. package/dist/hooks/manager.d.ts.map +1 -0
  13. package/dist/hooks/manager.js +395 -0
  14. package/dist/hooks/matcher.d.ts +49 -0
  15. package/dist/hooks/matcher.d.ts.map +1 -0
  16. package/dist/hooks/matcher.js +147 -0
  17. package/dist/hooks/settings.d.ts +46 -0
  18. package/dist/hooks/settings.d.ts.map +1 -0
  19. package/dist/hooks/settings.js +100 -0
  20. package/dist/hooks/types.d.ts +80 -0
  21. package/dist/hooks/types.d.ts.map +1 -0
  22. package/dist/hooks/types.js +59 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +20 -0
  26. package/dist/managers/aiManager.d.ts +61 -0
  27. package/dist/managers/aiManager.d.ts.map +1 -0
  28. package/dist/managers/aiManager.js +415 -0
  29. package/dist/managers/backgroundBashManager.d.ts +27 -0
  30. package/dist/managers/backgroundBashManager.d.ts.map +1 -0
  31. package/dist/managers/backgroundBashManager.js +166 -0
  32. package/dist/managers/bashManager.d.ts +20 -0
  33. package/dist/managers/bashManager.d.ts.map +1 -0
  34. package/dist/managers/bashManager.js +66 -0
  35. package/dist/managers/mcpManager.d.ts +63 -0
  36. package/dist/managers/mcpManager.d.ts.map +1 -0
  37. package/dist/managers/mcpManager.js +378 -0
  38. package/dist/managers/messageManager.d.ts +85 -0
  39. package/dist/managers/messageManager.d.ts.map +1 -0
  40. package/dist/managers/messageManager.js +265 -0
  41. package/dist/managers/skillManager.d.ts +59 -0
  42. package/dist/managers/skillManager.d.ts.map +1 -0
  43. package/dist/managers/skillManager.js +317 -0
  44. package/dist/managers/slashCommandManager.d.ts +77 -0
  45. package/dist/managers/slashCommandManager.d.ts.map +1 -0
  46. package/dist/managers/slashCommandManager.js +208 -0
  47. package/dist/managers/toolManager.d.ts +23 -0
  48. package/dist/managers/toolManager.d.ts.map +1 -0
  49. package/dist/managers/toolManager.js +79 -0
  50. package/dist/services/aiService.d.ts +28 -0
  51. package/dist/services/aiService.d.ts.map +1 -0
  52. package/dist/services/aiService.js +180 -0
  53. package/dist/services/memory.d.ts +8 -0
  54. package/dist/services/memory.d.ts.map +1 -0
  55. package/dist/services/memory.js +128 -0
  56. package/dist/services/session.d.ts +54 -0
  57. package/dist/services/session.d.ts.map +1 -0
  58. package/dist/services/session.js +196 -0
  59. package/dist/tools/bashTool.d.ts +14 -0
  60. package/dist/tools/bashTool.d.ts.map +1 -0
  61. package/dist/tools/bashTool.js +351 -0
  62. package/dist/tools/deleteFileTool.d.ts +6 -0
  63. package/dist/tools/deleteFileTool.d.ts.map +1 -0
  64. package/dist/tools/deleteFileTool.js +67 -0
  65. package/dist/tools/editTool.d.ts +6 -0
  66. package/dist/tools/editTool.d.ts.map +1 -0
  67. package/dist/tools/editTool.js +168 -0
  68. package/dist/tools/globTool.d.ts +6 -0
  69. package/dist/tools/globTool.d.ts.map +1 -0
  70. package/dist/tools/globTool.js +113 -0
  71. package/dist/tools/grepTool.d.ts +6 -0
  72. package/dist/tools/grepTool.d.ts.map +1 -0
  73. package/dist/tools/grepTool.js +268 -0
  74. package/dist/tools/lsTool.d.ts +6 -0
  75. package/dist/tools/lsTool.d.ts.map +1 -0
  76. package/dist/tools/lsTool.js +160 -0
  77. package/dist/tools/multiEditTool.d.ts +6 -0
  78. package/dist/tools/multiEditTool.d.ts.map +1 -0
  79. package/dist/tools/multiEditTool.js +222 -0
  80. package/dist/tools/readTool.d.ts +6 -0
  81. package/dist/tools/readTool.d.ts.map +1 -0
  82. package/dist/tools/readTool.js +136 -0
  83. package/dist/tools/types.d.ts +35 -0
  84. package/dist/tools/types.d.ts.map +1 -0
  85. package/dist/tools/types.js +4 -0
  86. package/dist/tools/writeTool.d.ts +6 -0
  87. package/dist/tools/writeTool.d.ts.map +1 -0
  88. package/dist/tools/writeTool.js +138 -0
  89. package/dist/types.d.ts +212 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +13 -0
  92. package/dist/utils/bashHistory.d.ts +46 -0
  93. package/dist/utils/bashHistory.d.ts.map +1 -0
  94. package/dist/utils/bashHistory.js +236 -0
  95. package/dist/utils/commandArgumentParser.d.ts +34 -0
  96. package/dist/utils/commandArgumentParser.d.ts.map +1 -0
  97. package/dist/utils/commandArgumentParser.js +123 -0
  98. package/dist/utils/constants.d.ts +27 -0
  99. package/dist/utils/constants.d.ts.map +1 -0
  100. package/dist/utils/constants.js +28 -0
  101. package/dist/utils/convertMessagesForAPI.d.ts +9 -0
  102. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -0
  103. package/dist/utils/convertMessagesForAPI.js +189 -0
  104. package/dist/utils/customCommands.d.ts +14 -0
  105. package/dist/utils/customCommands.d.ts.map +1 -0
  106. package/dist/utils/customCommands.js +71 -0
  107. package/dist/utils/fileFilter.d.ts +26 -0
  108. package/dist/utils/fileFilter.d.ts.map +1 -0
  109. package/dist/utils/fileFilter.js +177 -0
  110. package/dist/utils/markdownParser.d.ts +27 -0
  111. package/dist/utils/markdownParser.d.ts.map +1 -0
  112. package/dist/utils/markdownParser.js +109 -0
  113. package/dist/utils/mcpUtils.d.ts +24 -0
  114. package/dist/utils/mcpUtils.d.ts.map +1 -0
  115. package/dist/utils/mcpUtils.js +51 -0
  116. package/dist/utils/messageOperations.d.ts +118 -0
  117. package/dist/utils/messageOperations.d.ts.map +1 -0
  118. package/dist/utils/messageOperations.js +334 -0
  119. package/dist/utils/path.d.ts +25 -0
  120. package/dist/utils/path.d.ts.map +1 -0
  121. package/dist/utils/path.js +109 -0
  122. package/dist/utils/skillParser.d.ts +18 -0
  123. package/dist/utils/skillParser.d.ts.map +1 -0
  124. package/dist/utils/skillParser.js +147 -0
  125. package/dist/utils/stringUtils.d.ts +13 -0
  126. package/dist/utils/stringUtils.d.ts.map +1 -0
  127. package/dist/utils/stringUtils.js +44 -0
  128. package/package.json +51 -0
  129. package/src/agent.ts +405 -0
  130. package/src/hooks/executor.ts +440 -0
  131. package/src/hooks/index.ts +52 -0
  132. package/src/hooks/manager.ts +618 -0
  133. package/src/hooks/matcher.ts +187 -0
  134. package/src/hooks/settings.ts +129 -0
  135. package/src/hooks/types.ts +169 -0
  136. package/src/index.ts +24 -0
  137. package/src/managers/aiManager.ts +573 -0
  138. package/src/managers/backgroundBashManager.ts +203 -0
  139. package/src/managers/bashManager.ts +97 -0
  140. package/src/managers/mcpManager.ts +493 -0
  141. package/src/managers/messageManager.ts +415 -0
  142. package/src/managers/skillManager.ts +404 -0
  143. package/src/managers/slashCommandManager.ts +293 -0
  144. package/src/managers/toolManager.ts +106 -0
  145. package/src/services/aiService.ts +252 -0
  146. package/src/services/memory.ts +149 -0
  147. package/src/services/session.ts +265 -0
  148. package/src/tools/bashTool.ts +402 -0
  149. package/src/tools/deleteFileTool.ts +81 -0
  150. package/src/tools/editTool.ts +192 -0
  151. package/src/tools/globTool.ts +135 -0
  152. package/src/tools/grepTool.ts +326 -0
  153. package/src/tools/lsTool.ts +187 -0
  154. package/src/tools/multiEditTool.ts +268 -0
  155. package/src/tools/readTool.ts +165 -0
  156. package/src/tools/types.ts +47 -0
  157. package/src/tools/writeTool.ts +163 -0
  158. package/src/types.ts +260 -0
  159. package/src/utils/bashHistory.ts +303 -0
  160. package/src/utils/commandArgumentParser.ts +153 -0
  161. package/src/utils/constants.ts +37 -0
  162. package/src/utils/convertMessagesForAPI.ts +236 -0
  163. package/src/utils/customCommands.ts +85 -0
  164. package/src/utils/fileFilter.ts +202 -0
  165. package/src/utils/markdownParser.ts +156 -0
  166. package/src/utils/mcpUtils.ts +81 -0
  167. package/src/utils/messageOperations.ts +506 -0
  168. package/src/utils/path.ts +118 -0
  169. package/src/utils/skillParser.ts +188 -0
  170. package/src/utils/stringUtils.ts +50 -0
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Hook Manager
3
+ *
4
+ * Central orchestrator for the hooks system. Handles configuration loading,
5
+ * validation, and hook execution across all supported events.
6
+ */
7
+ import { HookConfigurationError, isValidHookEvent, isValidHookEventConfig, } from "./types.js";
8
+ import { HookMatcher } from "./matcher.js";
9
+ import { HookExecutor } from "./executor.js";
10
+ import { loadMergedHooksConfig } from "./settings.js";
11
+ export class HookManager {
12
+ constructor(workdir, matcher = new HookMatcher(), executor, logger) {
13
+ this.workdir = workdir;
14
+ this.matcher = matcher;
15
+ // Create executor with logger if provided, or use passed executor, or create default
16
+ this.executor = logger
17
+ ? new HookExecutor(logger)
18
+ : executor || new HookExecutor();
19
+ this.logger = logger;
20
+ }
21
+ /**
22
+ * Load and merge hook configurations from user and project settings
23
+ * Project settings take precedence over user settings
24
+ */
25
+ loadConfiguration(userHooks, projectHooks) {
26
+ const merged = {};
27
+ // Start with user hooks
28
+ if (userHooks) {
29
+ this.mergeHooksConfiguration(merged, userHooks);
30
+ }
31
+ // Override with project hooks (project settings take precedence)
32
+ if (projectHooks) {
33
+ this.mergeHooksConfiguration(merged, projectHooks);
34
+ }
35
+ // Validate merged configuration
36
+ const validation = this.validatePartialConfiguration(merged);
37
+ if (!validation.valid) {
38
+ throw new HookConfigurationError("merged configuration", validation.errors);
39
+ }
40
+ this.configuration = merged;
41
+ }
42
+ /**
43
+ * Load configuration from filesystem settings
44
+ * Automatically loads and merges user and project hooks configuration
45
+ */
46
+ loadConfigurationFromSettings() {
47
+ try {
48
+ this.logger?.debug(`[HookManager] Loading configuration...`);
49
+ const mergedConfig = loadMergedHooksConfig(this.workdir);
50
+ this.logger?.debug(`[HookManager] Merged config result:`, mergedConfig);
51
+ this.configuration = mergedConfig;
52
+ // Validate the loaded configuration
53
+ const validation = this.validatePartialConfiguration(mergedConfig);
54
+ if (!validation.valid) {
55
+ throw new HookConfigurationError("filesystem settings", validation.errors);
56
+ }
57
+ this.logger?.debug(`[HookManager] Configuration loaded successfully with ${Object.keys(mergedConfig).length} event types`);
58
+ }
59
+ catch (error) {
60
+ // If loading fails, start with undefined configuration (no hooks)
61
+ this.configuration = undefined;
62
+ // Re-throw configuration errors, but handle file system errors gracefully
63
+ if (error instanceof HookConfigurationError) {
64
+ throw error;
65
+ }
66
+ else {
67
+ this.logger?.warn("Failed to load hooks configuration from settings:", error);
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * Execute hooks for a specific event
73
+ */
74
+ async executeHooks(event, context) {
75
+ // Validate execution context
76
+ const contextValidation = this.validateExecutionContext(event, context);
77
+ if (!contextValidation.valid) {
78
+ this.logger?.error(`[HookManager] Invalid execution context for ${event}: ${contextValidation.errors.join(", ")}`);
79
+ return [
80
+ {
81
+ success: false,
82
+ stderr: `Invalid execution context: ${contextValidation.errors.join(", ")}`,
83
+ duration: 0,
84
+ timedOut: false,
85
+ },
86
+ ];
87
+ }
88
+ if (!this.configuration) {
89
+ this.logger?.debug(`[HookManager] No configuration loaded, skipping ${event} hooks`);
90
+ return [];
91
+ }
92
+ const eventConfigs = this.configuration[event];
93
+ if (!eventConfigs || eventConfigs.length === 0) {
94
+ this.logger?.debug(`[HookManager] No hooks configured for ${event} event`);
95
+ return [];
96
+ }
97
+ this.logger?.debug(`[HookManager] Starting ${event} hook execution with ${eventConfigs.length} configurations`);
98
+ const results = [];
99
+ const startTime = Date.now();
100
+ for (let configIndex = 0; configIndex < eventConfigs.length; configIndex++) {
101
+ const config = eventConfigs[configIndex];
102
+ // Check if this config applies to the current context
103
+ if (!this.configApplies(config, event, context.toolName)) {
104
+ this.logger?.debug(`[HookManager] Skipping configuration ${configIndex + 1}: matcher '${config.matcher}' does not match tool '${context.toolName}'`);
105
+ continue;
106
+ }
107
+ this.logger?.debug(`[HookManager] Executing configuration ${configIndex + 1} with ${config.hooks.length} commands (matcher: ${config.matcher || "any"})`);
108
+ // Execute all commands for this configuration
109
+ for (let commandIndex = 0; commandIndex < config.hooks.length; commandIndex++) {
110
+ const hookCommand = config.hooks[commandIndex];
111
+ try {
112
+ this.logger?.debug(`[HookManager] Executing command ${commandIndex + 1}/${config.hooks.length} in configuration ${configIndex + 1}`);
113
+ const result = await this.executor.executeCommand(hookCommand.command, context);
114
+ results.push(result);
115
+ // Report individual command result
116
+ if (result.success) {
117
+ this.logger?.debug(`[HookManager] Command ${commandIndex + 1} completed successfully in ${result.duration}ms`);
118
+ }
119
+ else {
120
+ this.logger?.warn(`[HookManager] Command ${commandIndex + 1} failed in ${result.duration}ms (exit code: ${result.exitCode}, timed out: ${result.timedOut})`);
121
+ }
122
+ // Continue with next command even if this one fails
123
+ // This allows for non-critical hooks to fail without stopping the workflow
124
+ }
125
+ catch (error) {
126
+ // This should be rare as executor handles most errors
127
+ const errorMessage = error instanceof Error ? error.message : "Unknown execution error";
128
+ this.logger?.error(`[HookManager] Unexpected error in command ${commandIndex + 1}: ${errorMessage}`);
129
+ results.push({
130
+ success: false,
131
+ stderr: errorMessage,
132
+ duration: 0,
133
+ timedOut: false,
134
+ });
135
+ }
136
+ }
137
+ }
138
+ // Generate execution summary
139
+ const totalDuration = Date.now() - startTime;
140
+ const summary = this.generateExecutionSummary(event, results, totalDuration);
141
+ this.logger?.info(`[HookManager] ${event} execution summary: ${summary}`);
142
+ return results;
143
+ }
144
+ /**
145
+ * Check if hooks are configured for an event/tool combination
146
+ */
147
+ hasHooks(event, toolName) {
148
+ if (!this.configuration)
149
+ return false;
150
+ const eventConfigs = this.configuration[event];
151
+ if (!eventConfigs || eventConfigs.length === 0)
152
+ return false;
153
+ return eventConfigs.some((config) => this.configApplies(config, event, toolName));
154
+ }
155
+ /**
156
+ * Validate hook configuration structure and content
157
+ */
158
+ validateConfiguration(config) {
159
+ const errors = [];
160
+ if (!config || typeof config !== "object") {
161
+ return { valid: false, errors: ["Configuration must be an object"] };
162
+ }
163
+ if (!config.hooks || typeof config.hooks !== "object") {
164
+ return {
165
+ valid: false,
166
+ errors: ["Configuration must have a hooks property"],
167
+ };
168
+ }
169
+ // Validate each hook event
170
+ for (const [eventName, eventConfigs] of Object.entries(config.hooks)) {
171
+ // Validate event name
172
+ if (!isValidHookEvent(eventName)) {
173
+ errors.push(`Invalid hook event: ${eventName}`);
174
+ continue;
175
+ }
176
+ // Validate event configurations
177
+ if (!Array.isArray(eventConfigs)) {
178
+ errors.push(`Hook event ${eventName} must be an array of configurations`);
179
+ continue;
180
+ }
181
+ eventConfigs.forEach((eventConfig, index) => {
182
+ const configErrors = this.validateEventConfig(eventName, eventConfig, index);
183
+ errors.push(...configErrors);
184
+ });
185
+ }
186
+ return {
187
+ valid: errors.length === 0,
188
+ errors,
189
+ };
190
+ }
191
+ /**
192
+ * Validate partial hook configuration structure and content
193
+ */
194
+ validatePartialConfiguration(config) {
195
+ const errors = [];
196
+ if (!config || typeof config !== "object") {
197
+ return { valid: false, errors: ["Configuration must be an object"] };
198
+ }
199
+ // Validate each hook event that is present
200
+ for (const [eventName, eventConfigs] of Object.entries(config)) {
201
+ // Validate event name
202
+ if (!isValidHookEvent(eventName)) {
203
+ errors.push(`Invalid hook event: ${eventName}`);
204
+ continue;
205
+ }
206
+ // Validate event configurations
207
+ if (!Array.isArray(eventConfigs)) {
208
+ errors.push(`Hook event ${eventName} must be an array of configurations`);
209
+ continue;
210
+ }
211
+ eventConfigs.forEach((eventConfig, index) => {
212
+ const configErrors = this.validateEventConfig(eventName, eventConfig, index);
213
+ errors.push(...configErrors);
214
+ });
215
+ }
216
+ return {
217
+ valid: errors.length === 0,
218
+ errors,
219
+ };
220
+ }
221
+ /**
222
+ * Get current configuration
223
+ */
224
+ getConfiguration() {
225
+ if (!this.configuration)
226
+ return undefined;
227
+ // Deep clone to prevent external modification
228
+ return JSON.parse(JSON.stringify(this.configuration));
229
+ }
230
+ /**
231
+ * Clear current configuration
232
+ */
233
+ clearConfiguration() {
234
+ this.configuration = undefined;
235
+ }
236
+ /**
237
+ * Validate execution context for a specific event
238
+ */
239
+ validateExecutionContext(event, context) {
240
+ const errors = [];
241
+ // Validate basic context structure
242
+ if (!context || typeof context !== "object") {
243
+ return { valid: false, errors: ["Context must be an object"] };
244
+ }
245
+ // Warn about event mismatch but don't fail validation
246
+ if (context.event !== event) {
247
+ this.logger?.warn(`[HookManager] Context event '${context.event}' does not match requested event '${event}'`);
248
+ }
249
+ // Validate project directory
250
+ if (!context.projectDir || typeof context.projectDir !== "string") {
251
+ errors.push("Context must have a valid projectDir string");
252
+ }
253
+ // Validate timestamp
254
+ if (!context.timestamp || !(context.timestamp instanceof Date)) {
255
+ errors.push("Context must have a valid timestamp Date object");
256
+ }
257
+ // Validate tool-specific requirements
258
+ if (event === "PreToolUse" || event === "PostToolUse") {
259
+ if (!context.toolName || typeof context.toolName !== "string") {
260
+ errors.push(`${event} event requires a valid toolName in context`);
261
+ }
262
+ }
263
+ // Validate non-tool events don't have unexpected tool names
264
+ if ((event === "UserPromptSubmit" || event === "Stop") &&
265
+ context.toolName !== undefined) {
266
+ this.logger?.warn(`[HookManager] ${event} event has unexpected toolName in context: ${context.toolName}`);
267
+ }
268
+ return {
269
+ valid: errors.length === 0,
270
+ errors,
271
+ };
272
+ }
273
+ /**
274
+ * Generate a summary of hook execution results
275
+ */
276
+ generateExecutionSummary(event, results, totalDuration) {
277
+ if (results.length === 0) {
278
+ return `No hooks executed for ${event}`;
279
+ }
280
+ const successful = results.filter((r) => r.success).length;
281
+ const failed = results.length - successful;
282
+ const timedOut = results.filter((r) => r.timedOut).length;
283
+ const avgDuration = results.reduce((sum, r) => sum + r.duration, 0) / results.length;
284
+ let summary = `${successful}/${results.length} commands successful`;
285
+ if (failed > 0) {
286
+ summary += `, ${failed} failed`;
287
+ }
288
+ if (timedOut > 0) {
289
+ summary += `, ${timedOut} timed out`;
290
+ }
291
+ summary += ` (avg: ${Math.round(avgDuration)}ms, total: ${totalDuration}ms)`;
292
+ return summary;
293
+ }
294
+ /**
295
+ * Merge hook configurations, with the second taking precedence
296
+ */
297
+ mergeHooksConfiguration(target, source) {
298
+ for (const [event, configs] of Object.entries(source)) {
299
+ if (isValidHookEvent(event)) {
300
+ // For now, completely replace event configs rather than merging
301
+ // This ensures project settings completely override user settings for each event
302
+ target[event] = [...configs];
303
+ }
304
+ }
305
+ }
306
+ /**
307
+ * Check if a hook configuration applies to the current context
308
+ */
309
+ configApplies(config, event, toolName) {
310
+ // For events that don't use matchers, config always applies
311
+ if (event === "UserPromptSubmit" || event === "Stop") {
312
+ return true;
313
+ }
314
+ // For tool-based events, check matcher if present
315
+ if (event === "PreToolUse" || event === "PostToolUse") {
316
+ if (!config.matcher) {
317
+ // No matcher means applies to all tools
318
+ return true;
319
+ }
320
+ if (!toolName) {
321
+ // No tool name provided, cannot match
322
+ return false;
323
+ }
324
+ return this.matcher.matches(config.matcher, toolName);
325
+ }
326
+ return false;
327
+ }
328
+ /**
329
+ * Validate a single event configuration
330
+ */
331
+ validateEventConfig(event, config, index) {
332
+ const errors = [];
333
+ const prefix = `Hook event ${event}[${index}]`;
334
+ if (!isValidHookEventConfig(config)) {
335
+ errors.push(`${prefix}: Invalid hook event configuration structure`);
336
+ return errors;
337
+ }
338
+ // Validate matcher requirements
339
+ if ((event === "PreToolUse" || event === "PostToolUse") && config.matcher) {
340
+ if (!this.matcher.isValidPattern(config.matcher)) {
341
+ errors.push(`${prefix}: Invalid matcher pattern: ${config.matcher}`);
342
+ }
343
+ }
344
+ // Validate that non-tool events don't have matchers
345
+ if ((event === "UserPromptSubmit" || event === "Stop") && config.matcher) {
346
+ errors.push(`${prefix}: Event ${event} should not have a matcher`);
347
+ }
348
+ // Validate commands
349
+ config.hooks.forEach((hookCommand, cmdIndex) => {
350
+ if (!this.executor.isCommandSafe(hookCommand.command)) {
351
+ errors.push(`${prefix}.hooks[${cmdIndex}]: Command may be unsafe: ${hookCommand.command}`);
352
+ }
353
+ });
354
+ return errors;
355
+ }
356
+ /**
357
+ * Get statistics about current configuration
358
+ */
359
+ getConfigurationStats() {
360
+ if (!this.configuration) {
361
+ return {
362
+ totalEvents: 0,
363
+ totalConfigs: 0,
364
+ totalCommands: 0,
365
+ eventBreakdown: {
366
+ PreToolUse: 0,
367
+ PostToolUse: 0,
368
+ UserPromptSubmit: 0,
369
+ Stop: 0,
370
+ },
371
+ };
372
+ }
373
+ const eventBreakdown = {
374
+ PreToolUse: 0,
375
+ PostToolUse: 0,
376
+ UserPromptSubmit: 0,
377
+ Stop: 0,
378
+ };
379
+ let totalConfigs = 0;
380
+ let totalCommands = 0;
381
+ Object.entries(this.configuration).forEach(([event, configs]) => {
382
+ if (isValidHookEvent(event)) {
383
+ eventBreakdown[event] = configs.length;
384
+ totalConfigs += configs.length;
385
+ totalCommands += configs.reduce((sum, config) => sum + config.hooks.length, 0);
386
+ }
387
+ });
388
+ return {
389
+ totalEvents: Object.keys(this.configuration).length,
390
+ totalConfigs,
391
+ totalCommands,
392
+ eventBreakdown,
393
+ };
394
+ }
395
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Hook Pattern Matcher
3
+ *
4
+ * Provides pattern matching functionality for hook tool name matching.
5
+ * Supports exact matching, wildcard patterns, and pipe-separated alternatives.
6
+ */
7
+ export interface IHookMatcher {
8
+ matches(pattern: string, toolName: string): boolean;
9
+ isValidPattern(pattern: string): boolean;
10
+ getPatternType(pattern: string): "exact" | "glob" | "regex" | "alternatives";
11
+ }
12
+ export declare class HookMatcher implements IHookMatcher {
13
+ /**
14
+ * Test if pattern matches tool name
15
+ * Supports multiple matching strategies:
16
+ * - Exact matching: "Edit" matches "Edit"
17
+ * - Pipe alternatives: "Edit|Write" matches "Edit" or "Write"
18
+ * - Glob patterns: "Edit*" matches "EditFile", "EditText", etc.
19
+ * - Case insensitive matching
20
+ */
21
+ matches(pattern: string, toolName: string): boolean;
22
+ /**
23
+ * Match a single pattern against tool name
24
+ */
25
+ private matchesSingle;
26
+ /**
27
+ * Validate pattern syntax
28
+ */
29
+ isValidPattern(pattern: string): boolean;
30
+ /**
31
+ * Validate single pattern syntax
32
+ */
33
+ private isValidSinglePattern;
34
+ /**
35
+ * Get pattern type for optimization
36
+ */
37
+ getPatternType(pattern: string): "exact" | "glob" | "regex" | "alternatives";
38
+ /**
39
+ * Get all tool names that would match this pattern from a given list
40
+ * Useful for testing and validation
41
+ */
42
+ getMatches(pattern: string, toolNames: string[]): string[];
43
+ /**
44
+ * Optimize pattern for repeated matching
45
+ * Returns a compiled matcher function for performance
46
+ */
47
+ compile(pattern: string): (toolName: string) => boolean;
48
+ }
49
+ //# sourceMappingURL=matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../../src/hooks/matcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,YAAY;IAE3B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAGpD,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAGzC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,cAAc,CAAC;CAC9E;AAED,qBAAa,WAAY,YAAW,YAAY;IAC9C;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAYnD;;OAEG;IACH,OAAO,CAAC,aAAa;IAmBrB;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAoBxC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAmB5B;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,cAAc;IA6B5E;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAI1D;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO;CAkCxD"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Hook Pattern Matcher
3
+ *
4
+ * Provides pattern matching functionality for hook tool name matching.
5
+ * Supports exact matching, wildcard patterns, and pipe-separated alternatives.
6
+ */
7
+ import { minimatch } from "minimatch";
8
+ export class HookMatcher {
9
+ /**
10
+ * Test if pattern matches tool name
11
+ * Supports multiple matching strategies:
12
+ * - Exact matching: "Edit" matches "Edit"
13
+ * - Pipe alternatives: "Edit|Write" matches "Edit" or "Write"
14
+ * - Glob patterns: "Edit*" matches "EditFile", "EditText", etc.
15
+ * - Case insensitive matching
16
+ */
17
+ matches(pattern, toolName) {
18
+ if (!pattern || !toolName)
19
+ return false;
20
+ // Handle pipe-separated alternatives (e.g., "Edit|Write|Delete")
21
+ if (pattern.includes("|")) {
22
+ const alternatives = pattern.split("|").map((alt) => alt.trim());
23
+ return alternatives.some((alt) => this.matchesSingle(alt, toolName));
24
+ }
25
+ return this.matchesSingle(pattern, toolName);
26
+ }
27
+ /**
28
+ * Match a single pattern against tool name
29
+ */
30
+ matchesSingle(pattern, toolName) {
31
+ // Exact match (case insensitive)
32
+ if (pattern.toLowerCase() === toolName.toLowerCase()) {
33
+ return true;
34
+ }
35
+ // Glob pattern matching using minimatch
36
+ try {
37
+ return minimatch(toolName, pattern, {
38
+ nocase: true, // Case insensitive
39
+ noglobstar: false, // Allow ** patterns
40
+ nonegate: true, // Disable negation for security
41
+ });
42
+ }
43
+ catch {
44
+ // Invalid pattern, fall back to exact match
45
+ return false;
46
+ }
47
+ }
48
+ /**
49
+ * Validate pattern syntax
50
+ */
51
+ isValidPattern(pattern) {
52
+ if (!pattern || typeof pattern !== "string")
53
+ return false;
54
+ // Empty pattern is invalid
55
+ if (pattern.trim().length === 0)
56
+ return false;
57
+ // Handle pipe-separated alternatives
58
+ if (pattern.includes("|")) {
59
+ const alternatives = pattern.split("|").map((alt) => alt.trim());
60
+ return (alternatives.length > 0 &&
61
+ alternatives.every((alt) => alt.length > 0 && this.isValidSinglePattern(alt)));
62
+ }
63
+ return this.isValidSinglePattern(pattern);
64
+ }
65
+ /**
66
+ * Validate single pattern syntax
67
+ */
68
+ isValidSinglePattern(pattern) {
69
+ // Basic validation - non-empty string
70
+ if (!pattern || pattern.trim().length === 0)
71
+ return false;
72
+ // Check for dangerous characters that could be used for command injection
73
+ // Note: [ ] are allowed for glob patterns
74
+ const dangerousChars = /[;&|`$(){}><]/;
75
+ if (dangerousChars.test(pattern))
76
+ return false;
77
+ // Validate glob pattern syntax using minimatch
78
+ try {
79
+ // Test with a dummy string to validate pattern
80
+ minimatch("test", pattern, { nocase: true });
81
+ return true;
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ }
87
+ /**
88
+ * Get pattern type for optimization
89
+ */
90
+ getPatternType(pattern) {
91
+ if (!pattern)
92
+ return "exact";
93
+ // Check for pipe alternatives first
94
+ if (pattern.includes("|")) {
95
+ return "alternatives";
96
+ }
97
+ // Check for regex patterns first (before glob check)
98
+ if (pattern.startsWith("/") &&
99
+ pattern.endsWith("/") &&
100
+ pattern.length > 2) {
101
+ return "regex";
102
+ }
103
+ // Check for glob patterns
104
+ if (pattern.includes("*") ||
105
+ pattern.includes("?") ||
106
+ pattern.includes("[")) {
107
+ return "glob";
108
+ }
109
+ return "exact";
110
+ }
111
+ /**
112
+ * Get all tool names that would match this pattern from a given list
113
+ * Useful for testing and validation
114
+ */
115
+ getMatches(pattern, toolNames) {
116
+ return toolNames.filter((toolName) => this.matches(pattern, toolName));
117
+ }
118
+ /**
119
+ * Optimize pattern for repeated matching
120
+ * Returns a compiled matcher function for performance
121
+ */
122
+ compile(pattern) {
123
+ if (!this.isValidPattern(pattern)) {
124
+ return () => false;
125
+ }
126
+ const patternType = this.getPatternType(pattern);
127
+ switch (patternType) {
128
+ case "exact": {
129
+ const lowerPattern = pattern.toLowerCase();
130
+ return (toolName) => toolName.toLowerCase() === lowerPattern;
131
+ }
132
+ case "alternatives": {
133
+ const alternatives = pattern
134
+ .split("|")
135
+ .map((alt) => alt.trim().toLowerCase());
136
+ return (toolName) => {
137
+ const lowerTool = toolName.toLowerCase();
138
+ return alternatives.some((alt) => lowerTool === alt || minimatch(toolName, alt, { nocase: true }));
139
+ };
140
+ }
141
+ case "glob":
142
+ return (toolName) => minimatch(toolName, pattern, { nocase: true });
143
+ default:
144
+ return (toolName) => this.matches(pattern, toolName);
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Hook Settings Management
3
+ *
4
+ * Handles loading and merging of hook configurations from:
5
+ * - User settings: ~/.wave/hooks.json
6
+ * - Project settings: ./.wave/hooks.json
7
+ */
8
+ import type { PartialHookConfiguration } from "./types.js";
9
+ /**
10
+ * Get the user-specific hooks configuration file path
11
+ */
12
+ export declare function getUserHooksConfigPath(): string;
13
+ /**
14
+ * Get the project-specific hooks configuration file path
15
+ */
16
+ export declare function getProjectHooksConfigPath(workdir: string): string;
17
+ /**
18
+ * Load hooks configuration from a JSON file
19
+ */
20
+ export declare function loadHooksConfigFromFile(filePath: string): PartialHookConfiguration | null;
21
+ /**
22
+ * Load user hooks configuration
23
+ */
24
+ export declare function loadUserHooksConfig(): PartialHookConfiguration | null;
25
+ /**
26
+ * Load project hooks configuration
27
+ */
28
+ export declare function loadProjectHooksConfig(workdir: string): PartialHookConfiguration | null;
29
+ /**
30
+ * Load and merge hooks configuration with project settings taking precedence
31
+ */
32
+ export declare function loadMergedHooksConfig(workdir: string): PartialHookConfiguration;
33
+ /**
34
+ * Check if any hooks configuration files exist
35
+ */
36
+ export declare function hasHooksConfiguration(workdir: string): boolean;
37
+ /**
38
+ * Get information about available hooks configuration files
39
+ */
40
+ export declare function getHooksConfigurationInfo(workdir: string): {
41
+ userConfigExists: boolean;
42
+ projectConfigExists: boolean;
43
+ userConfigPath: string;
44
+ projectConfigPath: string;
45
+ };
46
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/hooks/settings.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAqB,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG9E;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,GACf,wBAAwB,GAAG,IAAI,CAoBjC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,wBAAwB,GAAG,IAAI,CAErE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,GACd,wBAAwB,GAAG,IAAI,CAEjC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GACd,wBAAwB,CAyB1B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK9D;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG;IAC1D,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAOA"}