wave-agent-sdk 0.0.8 → 0.0.11

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 (236) hide show
  1. package/dist/agent.d.ts +92 -23
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +351 -137
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -0
  7. package/dist/managers/aiManager.d.ts +14 -36
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +74 -77
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +4 -3
  12. package/dist/managers/hookManager.d.ts +3 -8
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +39 -29
  15. package/dist/managers/liveConfigManager.d.ts +55 -18
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  17. package/dist/managers/liveConfigManager.js +372 -90
  18. package/dist/managers/lspManager.d.ts +43 -0
  19. package/dist/managers/lspManager.d.ts.map +1 -0
  20. package/dist/managers/lspManager.js +326 -0
  21. package/dist/managers/messageManager.d.ts +8 -16
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +52 -74
  24. package/dist/managers/permissionManager.d.ts +75 -0
  25. package/dist/managers/permissionManager.d.ts.map +1 -0
  26. package/dist/managers/permissionManager.js +368 -0
  27. package/dist/managers/skillManager.d.ts +1 -0
  28. package/dist/managers/skillManager.d.ts.map +1 -1
  29. package/dist/managers/skillManager.js +2 -1
  30. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  31. package/dist/managers/slashCommandManager.js +0 -1
  32. package/dist/managers/subagentManager.d.ts +8 -23
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +97 -117
  35. package/dist/managers/toolManager.d.ts +38 -1
  36. package/dist/managers/toolManager.d.ts.map +1 -1
  37. package/dist/managers/toolManager.js +66 -2
  38. package/dist/services/aiService.d.ts +3 -1
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +123 -30
  41. package/dist/services/configurationService.d.ts +116 -0
  42. package/dist/services/configurationService.d.ts.map +1 -0
  43. package/dist/services/configurationService.js +585 -0
  44. package/dist/services/fileWatcher.d.ts.map +1 -1
  45. package/dist/services/fileWatcher.js +5 -6
  46. package/dist/services/hook.d.ts +7 -124
  47. package/dist/services/hook.d.ts.map +1 -1
  48. package/dist/services/hook.js +46 -458
  49. package/dist/services/jsonlHandler.d.ts +24 -15
  50. package/dist/services/jsonlHandler.d.ts.map +1 -1
  51. package/dist/services/jsonlHandler.js +67 -88
  52. package/dist/services/memory.d.ts +0 -9
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +2 -49
  55. package/dist/services/session.d.ts +82 -33
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +275 -181
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +109 -11
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +25 -0
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +30 -6
  64. package/dist/tools/lspTool.d.ts +6 -0
  65. package/dist/tools/lspTool.d.ts.map +1 -0
  66. package/dist/tools/lspTool.js +589 -0
  67. package/dist/tools/multiEditTool.d.ts.map +1 -1
  68. package/dist/tools/multiEditTool.js +26 -7
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +111 -2
  71. package/dist/tools/skillTool.js +2 -2
  72. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  73. package/dist/tools/todoWriteTool.js +23 -0
  74. package/dist/tools/types.d.ts +11 -8
  75. package/dist/tools/types.d.ts.map +1 -1
  76. package/dist/tools/writeTool.d.ts.map +1 -1
  77. package/dist/tools/writeTool.js +25 -9
  78. package/dist/types/commands.d.ts +0 -1
  79. package/dist/types/commands.d.ts.map +1 -1
  80. package/dist/types/config.d.ts +4 -0
  81. package/dist/types/config.d.ts.map +1 -1
  82. package/dist/types/configuration.d.ts +69 -0
  83. package/dist/types/configuration.d.ts.map +1 -0
  84. package/dist/types/configuration.js +8 -0
  85. package/dist/types/core.d.ts +10 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +41 -0
  88. package/dist/types/environment.d.ts.map +1 -1
  89. package/dist/types/fileSearch.d.ts +5 -0
  90. package/dist/types/fileSearch.d.ts.map +1 -0
  91. package/dist/types/fileSearch.js +1 -0
  92. package/dist/types/hooks.d.ts +11 -2
  93. package/dist/types/hooks.d.ts.map +1 -1
  94. package/dist/types/hooks.js +1 -7
  95. package/dist/types/index.d.ts +5 -0
  96. package/dist/types/index.d.ts.map +1 -1
  97. package/dist/types/index.js +5 -0
  98. package/dist/types/lsp.d.ts +90 -0
  99. package/dist/types/lsp.d.ts.map +1 -0
  100. package/dist/types/lsp.js +4 -0
  101. package/dist/types/messaging.d.ts +6 -11
  102. package/dist/types/messaging.d.ts.map +1 -1
  103. package/dist/types/permissions.d.ts +39 -0
  104. package/dist/types/permissions.d.ts.map +1 -0
  105. package/dist/types/permissions.js +12 -0
  106. package/dist/types/session.d.ts +1 -6
  107. package/dist/types/session.d.ts.map +1 -1
  108. package/dist/types/skills.d.ts +1 -0
  109. package/dist/types/skills.d.ts.map +1 -1
  110. package/dist/types/tools.d.ts +35 -0
  111. package/dist/types/tools.d.ts.map +1 -0
  112. package/dist/types/tools.js +4 -0
  113. package/dist/utils/abortUtils.d.ts +34 -0
  114. package/dist/utils/abortUtils.d.ts.map +1 -0
  115. package/dist/utils/abortUtils.js +92 -0
  116. package/dist/utils/bashHistory.d.ts +4 -0
  117. package/dist/utils/bashHistory.d.ts.map +1 -1
  118. package/dist/utils/bashHistory.js +21 -4
  119. package/dist/utils/bashParser.d.ts +24 -0
  120. package/dist/utils/bashParser.d.ts.map +1 -0
  121. package/dist/utils/bashParser.js +413 -0
  122. package/dist/utils/builtinSubagents.d.ts +7 -0
  123. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  124. package/dist/utils/builtinSubagents.js +65 -0
  125. package/dist/utils/cacheControlUtils.d.ts +8 -33
  126. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  127. package/dist/utils/cacheControlUtils.js +83 -126
  128. package/dist/utils/constants.d.ts +0 -12
  129. package/dist/utils/constants.d.ts.map +1 -1
  130. package/dist/utils/constants.js +1 -13
  131. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  132. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  133. package/dist/utils/convertMessagesForAPI.js +33 -14
  134. package/dist/utils/fileSearch.d.ts +14 -0
  135. package/dist/utils/fileSearch.d.ts.map +1 -0
  136. package/dist/utils/fileSearch.js +88 -0
  137. package/dist/utils/fileUtils.d.ts +14 -2
  138. package/dist/utils/fileUtils.d.ts.map +1 -1
  139. package/dist/utils/fileUtils.js +101 -17
  140. package/dist/utils/globalLogger.d.ts +0 -14
  141. package/dist/utils/globalLogger.d.ts.map +1 -1
  142. package/dist/utils/globalLogger.js +0 -16
  143. package/dist/utils/markdownParser.d.ts.map +1 -1
  144. package/dist/utils/markdownParser.js +1 -17
  145. package/dist/utils/messageOperations.d.ts +1 -11
  146. package/dist/utils/messageOperations.d.ts.map +1 -1
  147. package/dist/utils/messageOperations.js +7 -24
  148. package/dist/utils/pathEncoder.d.ts +4 -0
  149. package/dist/utils/pathEncoder.d.ts.map +1 -1
  150. package/dist/utils/pathEncoder.js +16 -9
  151. package/dist/utils/pathSafety.d.ts +10 -0
  152. package/dist/utils/pathSafety.d.ts.map +1 -0
  153. package/dist/utils/pathSafety.js +23 -0
  154. package/dist/utils/subagentParser.d.ts +2 -2
  155. package/dist/utils/subagentParser.d.ts.map +1 -1
  156. package/dist/utils/subagentParser.js +10 -7
  157. package/package.json +9 -9
  158. package/src/agent.ts +475 -216
  159. package/src/index.ts +3 -0
  160. package/src/managers/aiManager.ts +107 -111
  161. package/src/managers/backgroundBashManager.ts +4 -3
  162. package/src/managers/hookManager.ts +44 -39
  163. package/src/managers/liveConfigManager.ts +524 -138
  164. package/src/managers/lspManager.ts +434 -0
  165. package/src/managers/messageManager.ts +73 -103
  166. package/src/managers/permissionManager.ts +480 -0
  167. package/src/managers/skillManager.ts +3 -1
  168. package/src/managers/slashCommandManager.ts +1 -2
  169. package/src/managers/subagentManager.ts +116 -159
  170. package/src/managers/toolManager.ts +95 -3
  171. package/src/services/aiService.ts +207 -26
  172. package/src/services/configurationService.ts +762 -0
  173. package/src/services/fileWatcher.ts +5 -6
  174. package/src/services/hook.ts +50 -631
  175. package/src/services/jsonlHandler.ts +84 -100
  176. package/src/services/memory.ts +2 -59
  177. package/src/services/session.ts +338 -213
  178. package/src/tools/bashTool.ts +126 -13
  179. package/src/tools/deleteFileTool.ts +36 -0
  180. package/src/tools/editTool.ts +41 -7
  181. package/src/tools/lspTool.ts +760 -0
  182. package/src/tools/multiEditTool.ts +37 -8
  183. package/src/tools/readTool.ts +125 -2
  184. package/src/tools/skillTool.ts +2 -2
  185. package/src/tools/todoWriteTool.ts +33 -1
  186. package/src/tools/types.ts +15 -9
  187. package/src/tools/writeTool.ts +36 -10
  188. package/src/types/commands.ts +0 -1
  189. package/src/types/config.ts +5 -0
  190. package/src/types/configuration.ts +73 -0
  191. package/src/types/core.ts +11 -0
  192. package/src/types/environment.ts +44 -0
  193. package/src/types/fileSearch.ts +4 -0
  194. package/src/types/hooks.ts +14 -11
  195. package/src/types/index.ts +5 -0
  196. package/src/types/lsp.ts +96 -0
  197. package/src/types/messaging.ts +8 -13
  198. package/src/types/permissions.ts +52 -0
  199. package/src/types/session.ts +3 -8
  200. package/src/types/skills.ts +1 -0
  201. package/src/types/tools.ts +38 -0
  202. package/src/utils/abortUtils.ts +118 -0
  203. package/src/utils/bashHistory.ts +28 -4
  204. package/src/utils/bashParser.ts +444 -0
  205. package/src/utils/builtinSubagents.ts +71 -0
  206. package/src/utils/cacheControlUtils.ts +106 -171
  207. package/src/utils/constants.ts +1 -16
  208. package/src/utils/convertMessagesForAPI.ts +38 -14
  209. package/src/utils/fileSearch.ts +107 -0
  210. package/src/utils/fileUtils.ts +114 -19
  211. package/src/utils/globalLogger.ts +0 -17
  212. package/src/utils/markdownParser.ts +1 -19
  213. package/src/utils/messageOperations.ts +7 -35
  214. package/src/utils/pathEncoder.ts +24 -9
  215. package/src/utils/pathSafety.ts +26 -0
  216. package/src/utils/subagentParser.ts +11 -8
  217. package/dist/constants/events.d.ts +0 -28
  218. package/dist/constants/events.d.ts.map +0 -1
  219. package/dist/constants/events.js +0 -27
  220. package/dist/services/configurationWatcher.d.ts +0 -120
  221. package/dist/services/configurationWatcher.d.ts.map +0 -1
  222. package/dist/services/configurationWatcher.js +0 -439
  223. package/dist/services/memoryStore.d.ts +0 -81
  224. package/dist/services/memoryStore.d.ts.map +0 -1
  225. package/dist/services/memoryStore.js +0 -200
  226. package/dist/types/memoryStore.d.ts +0 -82
  227. package/dist/types/memoryStore.d.ts.map +0 -1
  228. package/dist/types/memoryStore.js +0 -7
  229. package/dist/utils/configResolver.d.ts +0 -65
  230. package/dist/utils/configResolver.d.ts.map +0 -1
  231. package/dist/utils/configResolver.js +0 -210
  232. package/src/constants/events.ts +0 -38
  233. package/src/services/configurationWatcher.ts +0 -622
  234. package/src/services/memoryStore.ts +0 -279
  235. package/src/types/memoryStore.ts +0 -94
  236. package/src/utils/configResolver.ts +0 -302
@@ -1,37 +1,20 @@
1
1
  /**
2
- * Hook Services
2
+ * Hook Execution Services
3
3
  *
4
- * Consolidated hook services providing both execution and configuration functionality.
5
- * Combines hook command execution and settings management into a single module.
4
+ * Provides hook command execution functionality and hook-specific configuration loading.
5
+ * This module focuses on hook execution while delegating general Wave configuration
6
+ * management to ConfigurationService.
6
7
  */
7
8
 
8
9
  import { spawn, type ChildProcess } from "child_process";
9
- import { existsSync, readFileSync } from "fs";
10
- import {
11
- getUserConfigPath,
12
- getProjectConfigPath,
13
- getUserConfigPaths,
14
- getProjectConfigPaths,
15
- hasAnyConfig,
16
- getConfigurationInfo,
17
- } from "../utils/configPaths.js";
18
10
  import {
19
11
  type HookExecutionContext,
20
12
  type HookExecutionResult,
21
13
  type HookExecutionOptions,
22
14
  type ExtendedHookExecutionContext,
23
15
  type HookJsonInput,
24
- type WaveConfiguration,
25
- type PartialHookConfiguration,
26
- getSessionFilePath,
27
- isValidHookEvent,
28
16
  } from "../types/hooks.js";
29
- import {
30
- type EnvironmentValidationResult,
31
- type MergedEnvironmentContext,
32
- type EnvironmentMergeOptions,
33
- isValidEnvironmentVars,
34
- } from "../types/environment.js";
17
+ import { generateSessionFilePath } from "./session.js";
35
18
 
36
19
  // =============================================================================
37
20
  // Hook Execution Functions
@@ -40,15 +23,27 @@ import {
40
23
  /**
41
24
  * Build JSON input data for hook stdin
42
25
  */
43
- function buildHookJsonInput(
26
+ async function buildHookJsonInput(
44
27
  context: ExtendedHookExecutionContext,
45
- ): HookJsonInput {
28
+ ): Promise<HookJsonInput> {
29
+ const workdir = context.cwd || context.projectDir || process.cwd();
30
+
31
+ let transcriptPath = context.transcriptPath;
32
+ if (!transcriptPath && context.sessionId) {
33
+ try {
34
+ transcriptPath = await generateSessionFilePath(
35
+ context.sessionId,
36
+ workdir,
37
+ );
38
+ } catch {
39
+ transcriptPath = "";
40
+ }
41
+ }
42
+
46
43
  const jsonInput: HookJsonInput = {
47
44
  session_id: context.sessionId || "unknown",
48
- transcript_path:
49
- context.transcriptPath ||
50
- (context.sessionId ? getSessionFilePath(context.sessionId) : ""),
51
- cwd: context.cwd || context.projectDir,
45
+ transcript_path: transcriptPath || "",
46
+ cwd: workdir,
52
47
  hook_event_name: context.event,
53
48
  };
54
49
 
@@ -78,6 +73,16 @@ function buildHookJsonInput(
78
73
  jsonInput.subagent_type = context.subagentType;
79
74
  }
80
75
 
76
+ // Add notification fields for Notification events
77
+ if (context.event === "Notification") {
78
+ if (context.message !== undefined) {
79
+ jsonInput.message = context.message;
80
+ }
81
+ if (context.notificationType !== undefined) {
82
+ jsonInput.notification_type = context.notificationType;
83
+ }
84
+ }
85
+
81
86
  return jsonInput;
82
87
  }
83
88
 
@@ -88,7 +93,6 @@ export async function executeCommand(
88
93
  command: string,
89
94
  context: HookExecutionContext | ExtendedHookExecutionContext,
90
95
  options?: HookExecutionOptions,
91
- additionalEnvVars?: Record<string, string>,
92
96
  ): Promise<HookExecutionResult> {
93
97
  const defaultTimeout = 10000; // 10 seconds
94
98
  const maxTimeout = 300000; // 5 minutes
@@ -111,6 +115,17 @@ export async function executeCommand(
111
115
  };
112
116
  }
113
117
 
118
+ // Prepare JSON input for hooks that need it
119
+ let jsonInput: string | null = null;
120
+ if ("sessionId" in context) {
121
+ try {
122
+ const hookJsonInput = await buildHookJsonInput(context);
123
+ jsonInput = JSON.stringify(hookJsonInput, null, 2);
124
+ } catch {
125
+ // Continue execution even if JSON input preparation fails
126
+ }
127
+ }
128
+
114
129
  return new Promise((resolve) => {
115
130
  let stdout = "";
116
131
  let stderr = "";
@@ -125,8 +140,8 @@ export async function executeCommand(
125
140
  stdio: ["pipe", "pipe", "pipe"],
126
141
  cwd: context.projectDir,
127
142
  env: {
128
- ...process.env,
129
- ...additionalEnvVars, // Merge additional environment variables from Wave configuration
143
+ ...process.env, // Environment variables from process.env
144
+ ...("env" in context ? context.env || {} : {}), // Additional environment variables from configuration (if ExtendedHookExecutionContext)
130
145
  HOOK_EVENT: context.event,
131
146
  HOOK_TOOL_NAME: context.toolName || "",
132
147
  HOOK_PROJECT_DIR: context.projectDir,
@@ -160,11 +175,10 @@ export async function executeCommand(
160
175
  });
161
176
  }
162
177
 
163
- // Send JSON input to stdin if we have extended context
164
- if (childProcess.stdin && "sessionId" in context) {
178
+ // Send JSON input to stdin if we have prepared it
179
+ if (childProcess.stdin && jsonInput) {
165
180
  try {
166
- const jsonInput = buildHookJsonInput(context);
167
- childProcess.stdin.write(JSON.stringify(jsonInput, null, 2));
181
+ childProcess.stdin.write(jsonInput);
168
182
  childProcess.stdin.end();
169
183
  } catch {
170
184
  // Continue execution even if JSON input fails
@@ -212,17 +226,11 @@ export async function executeCommands(
212
226
  commands: string[],
213
227
  context: HookExecutionContext | ExtendedHookExecutionContext,
214
228
  options?: HookExecutionOptions,
215
- additionalEnvVars?: Record<string, string>,
216
229
  ): Promise<HookExecutionResult[]> {
217
230
  const results: HookExecutionResult[] = [];
218
231
 
219
232
  for (const command of commands) {
220
- const result = await executeCommand(
221
- command,
222
- context,
223
- options,
224
- additionalEnvVars,
225
- );
233
+ const result = await executeCommand(command, context, options);
226
234
  results.push(result);
227
235
 
228
236
  // Stop on first failure unless continueOnFailure is set
@@ -260,592 +268,3 @@ export function isCommandSafe(command: string): boolean {
260
268
  pattern.test(trimmed.toLowerCase()),
261
269
  );
262
270
  }
263
-
264
- // =============================================================================
265
- // Environment Variable Functions
266
- // =============================================================================
267
-
268
- /**
269
- * Validate environment variable configuration
270
- */
271
- export function validateEnvironmentConfig(
272
- env: unknown,
273
- configPath?: string,
274
- ): EnvironmentValidationResult {
275
- const result: EnvironmentValidationResult = {
276
- isValid: true,
277
- errors: [],
278
- warnings: [],
279
- };
280
-
281
- // Check if env is defined
282
- if (env === undefined || env === null) {
283
- return result; // undefined/null env is valid (means no env vars)
284
- }
285
-
286
- // Validate that env is a Record<string, string>
287
- if (!isValidEnvironmentVars(env)) {
288
- result.isValid = false;
289
- result.errors.push(
290
- `Invalid env field format${configPath ? ` in ${configPath}` : ""}. Environment variables must be a Record<string, string>.`,
291
- );
292
- return result;
293
- }
294
-
295
- // Additional validation for environment variable names
296
- const envVars = env as Record<string, string>;
297
- for (const [key, value] of Object.entries(envVars)) {
298
- // Check for valid environment variable naming convention
299
- if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) {
300
- result.warnings.push(
301
- `Environment variable '${key}' does not follow standard naming convention (alphanumeric and underscores only).`,
302
- );
303
- }
304
-
305
- // Check for empty values
306
- if (value === "") {
307
- result.warnings.push(`Environment variable '${key}' has an empty value.`);
308
- }
309
-
310
- // Check for reserved variable names that might cause conflicts
311
- const reservedNames = [
312
- "PATH",
313
- "HOME",
314
- "USER",
315
- "PWD",
316
- "SHELL",
317
- "TERM",
318
- "NODE_ENV",
319
- ];
320
- if (reservedNames.includes(key.toUpperCase())) {
321
- result.warnings.push(
322
- `Environment variable '${key}' overrides a system variable, which may cause unexpected behavior.`,
323
- );
324
- }
325
- }
326
-
327
- return result;
328
- }
329
-
330
- /**
331
- * Merge environment configurations with project taking precedence over user
332
- */
333
- export function mergeEnvironmentConfig(
334
- userEnv: Record<string, string> | undefined,
335
- projectEnv: Record<string, string> | undefined,
336
- options: EnvironmentMergeOptions = {},
337
- ): MergedEnvironmentContext {
338
- const userVars = userEnv || {};
339
- const projectVars = projectEnv || {};
340
- const mergedVars: Record<string, string> = {};
341
- const conflicts: MergedEnvironmentContext["conflicts"] = [];
342
-
343
- // Start with user environment variables
344
- Object.assign(mergedVars, userVars);
345
-
346
- // Override with project environment variables and track conflicts
347
- for (const [key, projectValue] of Object.entries(projectVars)) {
348
- const userValue = userVars[key];
349
-
350
- if (
351
- userValue !== undefined &&
352
- userValue !== projectValue &&
353
- options.includeConflictWarnings !== false
354
- ) {
355
- // Conflict detected - project value takes precedence
356
- conflicts.push({
357
- key,
358
- userValue,
359
- projectValue,
360
- resolvedValue: projectValue,
361
- });
362
- }
363
-
364
- mergedVars[key] = projectValue;
365
- }
366
-
367
- return {
368
- userVars,
369
- projectVars,
370
- mergedVars,
371
- conflicts,
372
- };
373
- }
374
-
375
- // =============================================================================
376
- // Hook Settings Functions (using centralized config path utilities)
377
- // =============================================================================
378
-
379
- /**
380
- * Get the user-specific hooks configuration file path (legacy function)
381
- * @deprecated Use getUserConfigPaths() from configPaths.ts for better priority support
382
- */
383
- export function getUserHooksConfigPath(): string {
384
- return getUserConfigPath();
385
- }
386
-
387
- /**
388
- * Get the project-specific hooks configuration file path (legacy function)
389
- * @deprecated Use getProjectConfigPaths() from configPaths.ts for better priority support
390
- */
391
- export function getProjectHooksConfigPath(workdir: string): string {
392
- return getProjectConfigPath(workdir);
393
- }
394
-
395
- /**
396
- * Get the user-specific hooks configuration file paths in priority order
397
- * @deprecated Use getUserConfigPaths() from configPaths.ts directly
398
- */
399
- export function getUserHooksConfigPaths(): string[] {
400
- return getUserConfigPaths();
401
- }
402
-
403
- /**
404
- * Get the project-specific hooks configuration file paths in priority order
405
- * @deprecated Use getProjectConfigPaths() from configPaths.ts directly
406
- */
407
- export function getProjectHooksConfigPaths(workdir: string): string[] {
408
- return getProjectConfigPaths(workdir);
409
- }
410
-
411
- /**
412
- * Load Wave configuration from a JSON file with graceful fallback
413
- * This version is optimized for live reload scenarios where invalid config should not crash the system
414
- */
415
- export function loadWaveConfigFromFileWithFallback(
416
- filePath: string,
417
- previousValidConfig?: WaveConfiguration | null,
418
- ): { config: WaveConfiguration | null; error?: string; usedFallback: boolean } {
419
- if (!existsSync(filePath)) {
420
- return { config: null, usedFallback: false };
421
- }
422
-
423
- try {
424
- const content = readFileSync(filePath, "utf-8");
425
- const config = JSON.parse(content) as WaveConfiguration;
426
-
427
- // Validate basic structure
428
- if (!config || typeof config !== "object") {
429
- const error = `Invalid configuration structure in ${filePath}`;
430
- return {
431
- config: previousValidConfig || null,
432
- error,
433
- usedFallback: !!previousValidConfig,
434
- };
435
- }
436
-
437
- // Validate environment variables if present
438
- if (config.env !== undefined) {
439
- const envValidation = validateEnvironmentConfig(config.env, filePath);
440
-
441
- if (!envValidation.isValid) {
442
- const error = `Environment variable validation failed in ${filePath}: ${envValidation.errors.join(", ")}`;
443
- return {
444
- config: previousValidConfig || null,
445
- error,
446
- usedFallback: !!previousValidConfig,
447
- };
448
- }
449
-
450
- // Log warnings if any
451
- if (envValidation.warnings.length > 0) {
452
- console.warn(
453
- `Environment variable warnings in ${filePath}:\n- ${envValidation.warnings.join("\n- ")}`,
454
- );
455
- }
456
- }
457
-
458
- // Return valid configuration
459
- return {
460
- config: {
461
- hooks: config.hooks || undefined,
462
- env: config.env || undefined,
463
- },
464
- usedFallback: false,
465
- };
466
- } catch (error) {
467
- let errorMessage: string;
468
-
469
- if (error instanceof SyntaxError) {
470
- errorMessage = `Invalid JSON syntax in ${filePath}: ${error.message}`;
471
- } else {
472
- errorMessage = `Error loading configuration from ${filePath}: ${(error as Error).message}`;
473
- }
474
-
475
- return {
476
- config: previousValidConfig || null,
477
- error: errorMessage,
478
- usedFallback: !!previousValidConfig,
479
- };
480
- }
481
- }
482
-
483
- /**
484
- * Load Wave configuration from multiple file paths in priority order
485
- * Returns the first valid configuration found, or null if none exist
486
- */
487
- export function loadWaveConfigFromFiles(
488
- filePaths: string[],
489
- ): WaveConfiguration | null {
490
- for (const filePath of filePaths) {
491
- const config = loadWaveConfigFromFile(filePath);
492
- if (config !== null) {
493
- return config;
494
- }
495
- }
496
- return null;
497
- }
498
-
499
- /**
500
- * Load Wave configuration from multiple file paths with graceful fallback
501
- * Returns the first valid configuration found with fallback support
502
- */
503
- export function loadWaveConfigFromFilesWithFallback(
504
- filePaths: string[],
505
- previousValidConfig?: WaveConfiguration | null,
506
- ): {
507
- config: WaveConfiguration | null;
508
- error?: string;
509
- usedFallback: boolean;
510
- usedPath?: string;
511
- } {
512
- let lastError: string | undefined;
513
-
514
- for (const filePath of filePaths) {
515
- const result = loadWaveConfigFromFileWithFallback(
516
- filePath,
517
- previousValidConfig,
518
- );
519
-
520
- if (result.config !== null && !result.usedFallback) {
521
- // Found a valid config at this path
522
- return {
523
- config: result.config,
524
- error: result.error,
525
- usedFallback: result.usedFallback,
526
- usedPath: filePath,
527
- };
528
- }
529
-
530
- if (result.error) {
531
- lastError = result.error;
532
- }
533
- }
534
-
535
- // No valid config found in any path
536
- return {
537
- config: previousValidConfig || null,
538
- error: lastError,
539
- usedFallback: !!previousValidConfig,
540
- };
541
- }
542
-
543
- /**
544
- * Load and merge Wave configuration with graceful fallback for live reload
545
- * Provides error recovery by falling back to previous valid configuration
546
- */
547
- export function loadMergedWaveConfigWithFallback(
548
- workdir: string,
549
- previousValidConfig?: WaveConfiguration | null,
550
- ): {
551
- config: WaveConfiguration | null;
552
- errors: string[];
553
- usedFallback: boolean;
554
- } {
555
- const errors: string[] = [];
556
- let usedFallback = false;
557
-
558
- // Load user config with fallback (check .local.json first, then .json)
559
- const userResult = loadWaveConfigFromFilesWithFallback(
560
- getUserHooksConfigPaths(),
561
- previousValidConfig,
562
- );
563
- if (userResult.error) {
564
- errors.push(`User config: ${userResult.error}`);
565
- }
566
- if (userResult.usedFallback) {
567
- usedFallback = true;
568
- }
569
-
570
- // Load project config with fallback (check .local.json first, then .json)
571
- const projectResult = loadWaveConfigFromFilesWithFallback(
572
- getProjectHooksConfigPaths(workdir),
573
- previousValidConfig,
574
- );
575
- if (projectResult.error) {
576
- errors.push(`Project config: ${projectResult.error}`);
577
- }
578
- if (projectResult.usedFallback) {
579
- usedFallback = true;
580
- }
581
-
582
- const userConfig = userResult.config;
583
- const projectConfig = projectResult.config;
584
-
585
- // If both configs failed and no fallback available
586
- if (!userConfig && !projectConfig && errors.length > 0) {
587
- return {
588
- config: previousValidConfig || null,
589
- errors,
590
- usedFallback: !!previousValidConfig,
591
- };
592
- }
593
-
594
- // No configuration found at all
595
- if (!userConfig && !projectConfig) {
596
- return { config: null, errors, usedFallback };
597
- }
598
-
599
- // Only one configuration found
600
- if (!userConfig) return { config: projectConfig, errors, usedFallback };
601
- if (!projectConfig) return { config: userConfig, errors, usedFallback };
602
-
603
- // Merge configurations (project overrides user)
604
- try {
605
- const mergedHooks: PartialHookConfiguration = {};
606
-
607
- // Merge environment variables using the new mergeEnvironmentConfig function
608
- const environmentContext = mergeEnvironmentConfig(
609
- userConfig.env,
610
- projectConfig.env,
611
- { includeConflictWarnings: true },
612
- );
613
-
614
- // Merge hooks (combine arrays, project configs come after user configs)
615
- const allEvents = new Set([
616
- ...Object.keys(userConfig.hooks || {}),
617
- ...Object.keys(projectConfig.hooks || {}),
618
- ]);
619
-
620
- for (const event of allEvents) {
621
- if (!isValidHookEvent(event)) continue;
622
-
623
- const userEventConfigs = userConfig.hooks?.[event] || [];
624
- const projectEventConfigs = projectConfig.hooks?.[event] || [];
625
-
626
- // Project configurations take precedence
627
- mergedHooks[event] = [...userEventConfigs, ...projectEventConfigs];
628
- }
629
-
630
- const mergedConfig = {
631
- hooks: Object.keys(mergedHooks).length > 0 ? mergedHooks : undefined,
632
- env:
633
- Object.keys(environmentContext.mergedVars).length > 0
634
- ? environmentContext.mergedVars
635
- : undefined,
636
- };
637
-
638
- return { config: mergedConfig, errors, usedFallback };
639
- } catch (error) {
640
- errors.push(`Merge error: ${(error as Error).message}`);
641
- return {
642
- config: previousValidConfig || null,
643
- errors,
644
- usedFallback: !!previousValidConfig,
645
- };
646
- }
647
- }
648
-
649
- /**
650
- * Load Wave configuration from a JSON file
651
- * Supports both hooks and environment variables with proper validation
652
- */
653
- export function loadWaveConfigFromFile(
654
- filePath: string,
655
- ): WaveConfiguration | null {
656
- if (!existsSync(filePath)) {
657
- return null;
658
- }
659
-
660
- try {
661
- const content = readFileSync(filePath, "utf-8");
662
- const config = JSON.parse(content) as WaveConfiguration;
663
-
664
- // Validate basic structure
665
- if (!config || typeof config !== "object") {
666
- throw new Error(`Invalid configuration structure in ${filePath}`);
667
- }
668
-
669
- // Validate environment variables if present
670
- if (config.env !== undefined) {
671
- const envValidation = validateEnvironmentConfig(config.env, filePath);
672
-
673
- if (!envValidation.isValid) {
674
- throw new Error(
675
- `Environment variable validation failed in ${filePath}: ${envValidation.errors.join(", ")}`,
676
- );
677
- }
678
-
679
- // Log warnings if any
680
- if (envValidation.warnings.length > 0) {
681
- console.warn(
682
- `Environment variable warnings in ${filePath}:\n- ${envValidation.warnings.join("\n- ")}`,
683
- );
684
- }
685
- }
686
-
687
- return {
688
- hooks: config.hooks || undefined,
689
- env: config.env || undefined,
690
- };
691
- } catch (error) {
692
- if (error instanceof SyntaxError) {
693
- throw new Error(`Invalid JSON syntax in ${filePath}: ${error.message}`);
694
- }
695
-
696
- // Re-throw validation errors and other errors as-is
697
- throw error;
698
- }
699
- }
700
-
701
- /**
702
- * Load hooks configuration from a JSON file (legacy function)
703
- */
704
- export function loadHooksConfigFromFile(
705
- filePath: string,
706
- ): PartialHookConfiguration | null {
707
- const waveConfig = loadWaveConfigFromFile(filePath);
708
- if (!waveConfig) {
709
- return null;
710
- }
711
-
712
- return waveConfig.hooks || null;
713
- }
714
-
715
- /**
716
- * Load user-specific Wave configuration
717
- * Checks .local.json first, then falls back to .json
718
- */
719
- export function loadUserWaveConfig(): WaveConfiguration | null {
720
- return loadWaveConfigFromFiles(getUserHooksConfigPaths());
721
- }
722
-
723
- /**
724
- * Load project-specific Wave configuration
725
- * Checks .local.json first, then falls back to .json
726
- */
727
- export function loadProjectWaveConfig(
728
- workdir: string,
729
- ): WaveConfiguration | null {
730
- return loadWaveConfigFromFiles(getProjectHooksConfigPaths(workdir));
731
- }
732
-
733
- /**
734
- * Load user-specific hooks configuration (legacy function)
735
- */
736
- export function loadUserHooksConfig(): PartialHookConfiguration | null {
737
- const waveConfig = loadUserWaveConfig();
738
- return waveConfig?.hooks || null;
739
- }
740
-
741
- /**
742
- * Load project-specific hooks configuration (legacy function)
743
- */
744
- export function loadProjectHooksConfig(
745
- workdir: string,
746
- ): PartialHookConfiguration | null {
747
- const waveConfig = loadProjectWaveConfig(workdir);
748
- return waveConfig?.hooks || null;
749
- }
750
-
751
- /**
752
- * Load and merge Wave configuration from both user and project sources
753
- * Project configuration takes precedence over user configuration
754
- * Checks .local.json files first, then falls back to .json files
755
- */
756
- export function loadMergedWaveConfig(
757
- workdir: string,
758
- ): WaveConfiguration | null {
759
- const userConfig = loadUserWaveConfig();
760
- const projectConfig = loadProjectWaveConfig(workdir);
761
-
762
- // No configuration found
763
- if (!userConfig && !projectConfig) {
764
- return null;
765
- }
766
-
767
- // Only one configuration found
768
- if (!userConfig) return projectConfig;
769
- if (!projectConfig) return userConfig;
770
-
771
- // Merge configurations (project overrides user)
772
- const mergedHooks: PartialHookConfiguration = {};
773
-
774
- // Merge environment variables using the new mergeEnvironmentConfig function
775
- const environmentContext = mergeEnvironmentConfig(
776
- userConfig.env,
777
- projectConfig.env,
778
- { includeConflictWarnings: true },
779
- );
780
-
781
- // Log environment variable conflicts if any
782
- if (environmentContext.conflicts.length > 0) {
783
- console.warn(
784
- `Environment variable conflicts detected (project values take precedence):\n${environmentContext.conflicts
785
- .map(
786
- (conflict) =>
787
- `- ${conflict.key}: "${conflict.userValue}" → "${conflict.projectValue}"`,
788
- )
789
- .join("\n")}`,
790
- );
791
- }
792
-
793
- // Merge hooks (combine arrays, project configs come after user configs)
794
- const allEvents = new Set([
795
- ...Object.keys(userConfig.hooks || {}),
796
- ...Object.keys(projectConfig.hooks || {}),
797
- ]);
798
-
799
- for (const event of allEvents) {
800
- if (!isValidHookEvent(event)) continue;
801
-
802
- const userEventConfigs = userConfig.hooks?.[event] || [];
803
- const projectEventConfigs = projectConfig.hooks?.[event] || [];
804
-
805
- // Project configurations take precedence
806
- mergedHooks[event] = [...userEventConfigs, ...projectEventConfigs];
807
- }
808
-
809
- return {
810
- hooks: Object.keys(mergedHooks).length > 0 ? mergedHooks : undefined,
811
- env:
812
- Object.keys(environmentContext.mergedVars).length > 0
813
- ? environmentContext.mergedVars
814
- : undefined,
815
- };
816
- }
817
-
818
- /**
819
- * Load and merge hooks configuration from both user and project sources (legacy function)
820
- */
821
- export function loadMergedHooksConfig(
822
- workdir: string,
823
- ): PartialHookConfiguration | null {
824
- const waveConfig = loadMergedWaveConfig(workdir);
825
- return waveConfig?.hooks || null;
826
- }
827
-
828
- /**
829
- * Check if hooks configuration exists (user or project)
830
- * Checks both .local.json and .json variants
831
- * @deprecated Use hasAnyConfig() from configPaths.ts for better functionality
832
- */
833
- export function hasHooksConfiguration(workdir: string): boolean {
834
- return hasAnyConfig(workdir);
835
- }
836
-
837
- /**
838
- * Get hooks configuration information for debugging
839
- * Includes both .local.json and .json variants
840
- * @deprecated Use getConfigurationInfo() from configPaths.ts for better functionality
841
- */
842
- export function getHooksConfigurationInfo(workdir: string): {
843
- hasUser: boolean;
844
- hasProject: boolean;
845
- paths: string[];
846
- userPaths: string[];
847
- projectPaths: string[];
848
- existingPaths: string[];
849
- } {
850
- return getConfigurationInfo(workdir);
851
- }