alchemylab 1.0.2 → 1.0.4

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.
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ /**
3
+ * Hook Condition Evaluator
4
+ *
5
+ * Evaluates hook conditions against event variables to determine
6
+ * if a hook should be triggered.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.evaluateSingleCondition = evaluateSingleCondition;
10
+ exports.evaluatePathsCondition = evaluatePathsCondition;
11
+ exports.evaluateCondition = evaluateCondition;
12
+ /**
13
+ * Simple glob-to-regex converter for pattern matching
14
+ * Handles common glob patterns without external dependencies
15
+ */
16
+ function globToRegex(pattern) {
17
+ // Escape regex special chars except * and ?
18
+ let regex = pattern
19
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
20
+ // Replace ** with a placeholder
21
+ .replace(/\*\*/g, '{{GLOBSTAR}}')
22
+ // Replace * with [^/]* (anything except path separator)
23
+ .replace(/\*/g, '[^/]*')
24
+ // Replace ? with single char
25
+ .replace(/\?/g, '.')
26
+ // Replace globstar placeholder with .* (anything including /)
27
+ .replace(/\{\{GLOBSTAR\}\}/g, '.*');
28
+ return new RegExp(`^${regex}$`);
29
+ }
30
+ /**
31
+ * Check if a string matches a glob pattern
32
+ */
33
+ function isMatch(str, pattern) {
34
+ try {
35
+ return globToRegex(pattern).test(str);
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ /**
42
+ * Evaluate a single condition against the field value
43
+ */
44
+ function evaluateSingleCondition(fieldValue, operator, value) {
45
+ switch (operator) {
46
+ case 'matches':
47
+ return isMatch(fieldValue, value);
48
+ case 'not_matches':
49
+ return !isMatch(fieldValue, value);
50
+ case 'contains':
51
+ return fieldValue.includes(value);
52
+ case 'not_contains':
53
+ return !fieldValue.includes(value);
54
+ case 'equals':
55
+ return fieldValue === value;
56
+ case 'not_equals':
57
+ return fieldValue !== value;
58
+ default:
59
+ return true;
60
+ }
61
+ }
62
+ /**
63
+ * Evaluate a condition against a paths array (comma-separated)
64
+ * For 'matches'/'contains' - returns true if ANY path matches
65
+ * For 'not_matches'/'not_contains' - returns true if ALL paths don't match
66
+ */
67
+ function evaluatePathsCondition(paths, operator, value) {
68
+ switch (operator) {
69
+ case 'matches':
70
+ return paths.some(p => isMatch(p, value));
71
+ case 'not_matches':
72
+ return paths.every(p => !isMatch(p, value));
73
+ case 'contains':
74
+ return paths.some(p => p.includes(value));
75
+ case 'not_contains':
76
+ return paths.every(p => !p.includes(value));
77
+ case 'equals':
78
+ return paths.some(p => p === value);
79
+ case 'not_equals':
80
+ return paths.every(p => p !== value);
81
+ default:
82
+ return true;
83
+ }
84
+ }
85
+ /**
86
+ * Evaluate hook conditions against current variables
87
+ * Returns true if hook should be triggered, false to skip
88
+ */
89
+ function evaluateCondition(condition, variables) {
90
+ // No conditions or empty conditions = always match
91
+ if (!condition || !condition.conditions || condition.conditions.length === 0) {
92
+ return true;
93
+ }
94
+ const results = condition.conditions.map((c) => {
95
+ const fieldValue = variables[c.field] || '';
96
+ // For 'paths' field, check if any path matches
97
+ if (c.field === 'paths' && fieldValue.includes(', ')) {
98
+ const paths = fieldValue.split(', ');
99
+ return evaluatePathsCondition(paths, c.operator, c.value);
100
+ }
101
+ // Single value evaluation
102
+ return evaluateSingleCondition(fieldValue, c.operator, c.value);
103
+ });
104
+ // Combine results based on logic (default: AND)
105
+ return condition.logic === 'OR'
106
+ ? results.some((r) => r)
107
+ : results.every((r) => r);
108
+ }
109
+ //# sourceMappingURL=condition-evaluator.js.map
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ /**
3
+ * Deduplication Utilities
4
+ *
5
+ * Functions to prevent duplicate hook triggers when OpenCode fires
6
+ * the same event multiple times in quick succession.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.createToolCallHash = createToolCallHash;
10
+ exports.isDuplicateToolCall = isDuplicateToolCall;
11
+ exports.clearRecentCalls = clearRecentCalls;
12
+ exports.getRecentCallsCount = getRecentCallsCount;
13
+ // Track recent tool calls with timestamps
14
+ const recentCalls = new Map();
15
+ // Default window for considering calls as duplicates (2 seconds)
16
+ const DEFAULT_DEDUP_WINDOW = 2000;
17
+ /**
18
+ * Create a hash for a tool call based on tool name and output/input
19
+ * Used to identify potentially duplicate calls
20
+ *
21
+ * @param toolName - The name of the tool being called
22
+ * @param text - The output or input text to include in hash (truncated to 100 chars)
23
+ * @returns A string hash identifying this specific call
24
+ */
25
+ function createToolCallHash(toolName, text) {
26
+ // Simple hash using tool name + first 100 chars of text
27
+ return `${toolName}:${text?.substring(0, 100) || ''}`;
28
+ }
29
+ /**
30
+ * Check if a tool call is a duplicate of a recent call
31
+ * Also cleans up old entries from the tracking map
32
+ *
33
+ * @param hash - The hash created by createToolCallHash
34
+ * @param now - Current timestamp (allows injection for testing)
35
+ * @param dedupWindow - Time window in ms to consider calls as duplicates (default: 2000ms)
36
+ * @returns true if this appears to be a duplicate call, false otherwise
37
+ */
38
+ function isDuplicateToolCall(hash, now = Date.now(), dedupWindow = DEFAULT_DEDUP_WINDOW) {
39
+ const lastTime = recentCalls.get(hash);
40
+ if (lastTime && (now - lastTime) < dedupWindow) {
41
+ return true;
42
+ }
43
+ // Clean up old entries (older than 2x the dedup window)
44
+ const cleanupThreshold = dedupWindow * 2;
45
+ for (const [key, time] of recentCalls.entries()) {
46
+ if (now - time > cleanupThreshold) {
47
+ recentCalls.delete(key);
48
+ }
49
+ }
50
+ // Record this call
51
+ recentCalls.set(hash, now);
52
+ return false;
53
+ }
54
+ /**
55
+ * Clear all tracked calls (useful for testing)
56
+ */
57
+ function clearRecentCalls() {
58
+ recentCalls.clear();
59
+ }
60
+ /**
61
+ * Get the number of tracked calls (useful for testing)
62
+ */
63
+ function getRecentCallsCount() {
64
+ return recentCalls.size;
65
+ }
66
+ //# sourceMappingURL=dedup-utils.js.map
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ /**
3
+ * Hook Utility Functions
4
+ *
5
+ * Shared utilities for hook processing including template interpolation
6
+ * and ID extraction from tool outputs.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.interpolatePrompt = interpolatePrompt;
10
+ exports.extractRequestId = extractRequestId;
11
+ exports.extractTaskId = extractTaskId;
12
+ exports.extractFilePath = extractFilePath;
13
+ exports.sanitizeTemplateValue = sanitizeTemplateValue;
14
+ /**
15
+ * Interpolate template variables in prompt
16
+ * Replaces {{variableName}} patterns with corresponding values from variables object
17
+ *
18
+ * @param template - The template string with {{variable}} placeholders
19
+ * @param variables - Key-value pairs to substitute into the template
20
+ * @returns The interpolated string with all variables replaced
21
+ */
22
+ function interpolatePrompt(template, variables) {
23
+ let result = template;
24
+ for (const [key, value] of Object.entries(variables)) {
25
+ result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
26
+ }
27
+ return result;
28
+ }
29
+ /**
30
+ * Extract request ID from tool output
31
+ * Matches patterns like "Request completed: req001234"
32
+ *
33
+ * @param text - The tool output text to search
34
+ * @returns The request ID (e.g., "req001234") or null if not found
35
+ */
36
+ function extractRequestId(text) {
37
+ const match = text.match(/Request completed:\s*(req\d+)/i);
38
+ return match ? match[1] : null;
39
+ }
40
+ /**
41
+ * Extract task ID from tool output
42
+ * Matches patterns like "Task tas002152 has been marked as completed"
43
+ *
44
+ * @param text - The tool output text to search
45
+ * @returns The task ID (e.g., "tas002152") or null if not found
46
+ */
47
+ function extractTaskId(text) {
48
+ const match = text.match(/Task\s+(tas\d+).*(?:completed|marked as completed)/i);
49
+ return match ? match[1] : null;
50
+ }
51
+ /**
52
+ * Extract file path from write_file tool output
53
+ * Matches patterns like "Created file path/to/file.ts"
54
+ *
55
+ * @param text - The tool output text to search
56
+ * @returns The file path or null if not found
57
+ */
58
+ function extractFilePath(text) {
59
+ // Match "Created file X" or "**Created file X**"
60
+ const createdMatch = text.match(/\*?\*?Created file\s+([^\s*]+)/i);
61
+ if (createdMatch)
62
+ return createdMatch[1];
63
+ // Match "File Modified: Yes" with path context
64
+ const modifiedMatch = text.match(/File \d+:\s*([^\n]+)\n.*File Modified:\s*Yes/i);
65
+ if (modifiedMatch)
66
+ return modifiedMatch[1];
67
+ return null;
68
+ }
69
+ /**
70
+ * Sanitize a value for use in template interpolation
71
+ * Truncates long values and escapes special characters
72
+ *
73
+ * @param value - The value to sanitize
74
+ * @param maxLength - Maximum length before truncation (default: 1000)
75
+ * @returns The sanitized string
76
+ */
77
+ function sanitizeTemplateValue(value, maxLength = 1000) {
78
+ if (!value)
79
+ return '';
80
+ if (value.length > maxLength) {
81
+ return value.substring(0, maxLength) + '... (truncated)';
82
+ }
83
+ return value;
84
+ }
85
+ //# sourceMappingURL=hook-utils.js.map
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /**
3
+ * Hook Condition Evaluator
4
+ *
5
+ * Evaluates hook conditions against event variables to determine
6
+ * if a hook should be triggered.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.evaluateSingleCondition = evaluateSingleCondition;
10
+ exports.evaluatePathsCondition = evaluatePathsCondition;
11
+ exports.evaluateCondition = evaluateCondition;
12
+ /**
13
+ * Simple glob-to-regex converter for pattern matching
14
+ * Handles common glob patterns without external dependencies
15
+ */
16
+ // Cache compiled glob→regex patterns to avoid recompilation on every condition check
17
+ const _globCache = new Map();
18
+ function globToRegex(pattern) {
19
+ const cached = _globCache.get(pattern);
20
+ if (cached)
21
+ return cached;
22
+ let regex = pattern
23
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
24
+ .replace(/\*\*/g, '{{GLOBSTAR}}')
25
+ .replace(/\*/g, '[^/]*')
26
+ .replace(/\?/g, '.')
27
+ .replace(/\{\{GLOBSTAR\}\}/g, '.*');
28
+ const compiled = new RegExp(`^${regex}$`);
29
+ // Cap cache to prevent unbounded growth
30
+ if (_globCache.size > 500)
31
+ _globCache.clear();
32
+ _globCache.set(pattern, compiled);
33
+ return compiled;
34
+ }
35
+ /**
36
+ * Check if a string matches a glob pattern
37
+ */
38
+ function isMatch(str, pattern) {
39
+ try {
40
+ return globToRegex(pattern).test(str);
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ /**
47
+ * Evaluate a single condition against the field value
48
+ */
49
+ function evaluateSingleCondition(fieldValue, operator, value) {
50
+ switch (operator) {
51
+ case 'matches':
52
+ return isMatch(fieldValue, value);
53
+ case 'not_matches':
54
+ return !isMatch(fieldValue, value);
55
+ case 'contains':
56
+ return fieldValue.includes(value);
57
+ case 'not_contains':
58
+ return !fieldValue.includes(value);
59
+ case 'equals':
60
+ return fieldValue === value;
61
+ case 'not_equals':
62
+ return fieldValue !== value;
63
+ default:
64
+ return true;
65
+ }
66
+ }
67
+ /**
68
+ * Evaluate a condition against a paths array (comma-separated)
69
+ * For 'matches'/'contains' - returns true if ANY path matches
70
+ * For 'not_matches'/'not_contains' - returns true if ALL paths don't match
71
+ */
72
+ function evaluatePathsCondition(paths, operator, value) {
73
+ switch (operator) {
74
+ case 'matches':
75
+ return paths.some(p => isMatch(p, value));
76
+ case 'not_matches':
77
+ return paths.every(p => !isMatch(p, value));
78
+ case 'contains':
79
+ return paths.some(p => p.includes(value));
80
+ case 'not_contains':
81
+ return paths.every(p => !p.includes(value));
82
+ case 'equals':
83
+ return paths.some(p => p === value);
84
+ case 'not_equals':
85
+ return paths.every(p => p !== value);
86
+ default:
87
+ return true;
88
+ }
89
+ }
90
+ /**
91
+ * Evaluate hook conditions against current variables
92
+ * Returns true if hook should be triggered, false to skip
93
+ */
94
+ function evaluateCondition(condition, variables) {
95
+ // No conditions or empty conditions = always match
96
+ if (!condition || !condition.conditions || condition.conditions.length === 0) {
97
+ return true;
98
+ }
99
+ const results = condition.conditions.map((c) => {
100
+ const fieldValue = variables[c.field] || '';
101
+ // For 'paths' field, check if any path matches
102
+ if (c.field === 'paths' && fieldValue.includes(', ')) {
103
+ const paths = fieldValue.split(', ');
104
+ return evaluatePathsCondition(paths, c.operator, c.value);
105
+ }
106
+ // Single value evaluation
107
+ return evaluateSingleCondition(fieldValue, c.operator, c.value);
108
+ });
109
+ // Combine results based on logic (default: AND)
110
+ return condition.logic === 'OR'
111
+ ? results.some((r) => r)
112
+ : results.every((r) => r);
113
+ }
114
+ //# sourceMappingURL=condition-evaluator.js.map
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ /**
3
+ * Deduplication Utilities
4
+ *
5
+ * Functions to prevent duplicate hook triggers when OpenCode fires
6
+ * the same event multiple times in quick succession.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.createToolCallHash = createToolCallHash;
10
+ exports.isDuplicateToolCall = isDuplicateToolCall;
11
+ exports.clearRecentCalls = clearRecentCalls;
12
+ exports.getRecentCallsCount = getRecentCallsCount;
13
+ exports.hasProcessedMessage = hasProcessedMessage;
14
+ exports.markMessageProcessed = markMessageProcessed;
15
+ exports.tryMarkMessageProcessed = tryMarkMessageProcessed;
16
+ exports.clearProcessedMessages = clearProcessedMessages;
17
+ // Track recent tool calls with timestamps
18
+ const recentCalls = new Map();
19
+ // Default window for considering calls as duplicates (2 seconds)
20
+ const DEFAULT_DEDUP_WINDOW = 2000;
21
+ /**
22
+ * Create a hash for a tool call based on tool name and output/input
23
+ * Used to identify potentially duplicate calls
24
+ *
25
+ * @param toolName - The name of the tool being called
26
+ * @param text - The output or input text to include in hash (truncated to 100 chars)
27
+ * @returns A string hash identifying this specific call
28
+ */
29
+ function createToolCallHash(toolName, text) {
30
+ // Simple hash using tool name + first 100 chars of text
31
+ return `${toolName}:${text?.substring(0, 100) || ''}`;
32
+ }
33
+ /**
34
+ * Check if a tool call is a duplicate of a recent call
35
+ * Also cleans up old entries from the tracking map
36
+ *
37
+ * @param hash - The hash created by createToolCallHash
38
+ * @param now - Current timestamp (allows injection for testing)
39
+ * @param dedupWindow - Time window in ms to consider calls as duplicates (default: 2000ms)
40
+ * @returns true if this appears to be a duplicate call, false otherwise
41
+ */
42
+ function isDuplicateToolCall(hash, now = Date.now(), dedupWindow = DEFAULT_DEDUP_WINDOW) {
43
+ const lastTime = recentCalls.get(hash);
44
+ if (lastTime && (now - lastTime) < dedupWindow) {
45
+ return true;
46
+ }
47
+ // Clean up old entries (older than 2x the dedup window)
48
+ const cleanupThreshold = dedupWindow * 2;
49
+ for (const [key, time] of recentCalls.entries()) {
50
+ if (now - time > cleanupThreshold) {
51
+ recentCalls.delete(key);
52
+ }
53
+ }
54
+ // Record this call
55
+ recentCalls.set(hash, now);
56
+ return false;
57
+ }
58
+ /**
59
+ * Clear all tracked calls (useful for testing)
60
+ */
61
+ function clearRecentCalls() {
62
+ recentCalls.clear();
63
+ }
64
+ /**
65
+ * Get the number of tracked calls (useful for testing)
66
+ */
67
+ function getRecentCallsCount() {
68
+ return recentCalls.size;
69
+ }
70
+ // Track last processed message ID per session to prevent duplicate triggers
71
+ const lastProcessedMessageId = new Map();
72
+ /**
73
+ * Check if we've already processed this message for a session
74
+ * Returns true if this message was already processed (should skip)
75
+ * @deprecated Use tryMarkMessageProcessed for atomic check-and-mark
76
+ */
77
+ function hasProcessedMessage(sessionId, messageId) {
78
+ if (!messageId)
79
+ return false;
80
+ const lastId = lastProcessedMessageId.get(sessionId);
81
+ return lastId === messageId;
82
+ }
83
+ /**
84
+ * Mark a message as processed for a session
85
+ * @deprecated Use tryMarkMessageProcessed for atomic check-and-mark
86
+ */
87
+ function markMessageProcessed(sessionId, messageId) {
88
+ if (!messageId)
89
+ return;
90
+ lastProcessedMessageId.set(sessionId, messageId);
91
+ // Clean up old entries (keep max 100 sessions)
92
+ if (lastProcessedMessageId.size > 100) {
93
+ const firstKey = lastProcessedMessageId.keys().next().value;
94
+ if (firstKey)
95
+ lastProcessedMessageId.delete(firstKey);
96
+ }
97
+ }
98
+ /**
99
+ * Atomically check if message was processed and mark it if not.
100
+ * Returns true if this call should proceed (message was NOT already processed).
101
+ * Returns false if should skip (message was already processed by another call).
102
+ *
103
+ * This prevents race conditions where two concurrent calls both check
104
+ * hasProcessedMessage() before either calls markMessageProcessed().
105
+ */
106
+ function tryMarkMessageProcessed(sessionId, messageId) {
107
+ if (!messageId)
108
+ return true; // No message ID = proceed (can't dedupe)
109
+ const lastId = lastProcessedMessageId.get(sessionId);
110
+ if (lastId === messageId) {
111
+ // Already processed - skip
112
+ return false;
113
+ }
114
+ // Mark as processed immediately (atomic with check above in single-threaded JS)
115
+ lastProcessedMessageId.set(sessionId, messageId);
116
+ // Clean up old entries (keep max 100 sessions)
117
+ if (lastProcessedMessageId.size > 100) {
118
+ const firstKey = lastProcessedMessageId.keys().next().value;
119
+ if (firstKey)
120
+ lastProcessedMessageId.delete(firstKey);
121
+ }
122
+ return true; // Proceed - we claimed this message
123
+ }
124
+ /**
125
+ * Clear processed message tracking (for testing)
126
+ */
127
+ function clearProcessedMessages() {
128
+ lastProcessedMessageId.clear();
129
+ }
130
+ //# sourceMappingURL=dedup-utils.js.map
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Hook Utility Functions
4
+ *
5
+ * Shared utilities for hook processing including template interpolation
6
+ * and ID extraction from tool outputs.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.interpolatePrompt = interpolatePrompt;
10
+ exports.extractRequestId = extractRequestId;
11
+ exports.extractTaskId = extractTaskId;
12
+ exports.extractFilePath = extractFilePath;
13
+ exports.sanitizeTemplateValue = sanitizeTemplateValue;
14
+ /**
15
+ * Interpolate template variables in prompt
16
+ * Replaces {{variableName}} patterns with corresponding values from variables object
17
+ *
18
+ * @param template - The template string with {{variable}} placeholders
19
+ * @param variables - Key-value pairs to substitute into the template
20
+ * @returns The interpolated string with all variables replaced
21
+ */
22
+ function interpolatePrompt(template, variables) {
23
+ let result = template;
24
+ for (const [key, value] of Object.entries(variables)) {
25
+ result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
26
+ }
27
+ return result;
28
+ }
29
+ /**
30
+ * Extract request ID from tool output
31
+ * Matches patterns like "Request completed: req001234" or "Request req001234 completed successfully"
32
+ *
33
+ * @param text - The tool output text to search
34
+ * @returns The request ID (e.g., "req001234") or null if not found
35
+ */
36
+ function extractRequestId(text) {
37
+ const patterns = [
38
+ /Request completed:\s*(req\d+)/i,
39
+ /Request (req\d+) completed/i,
40
+ /completed.*?(req\d{6})/i,
41
+ /Request finalized:\s*(req\d+)/i,
42
+ /Request (req\d+) finalized/i,
43
+ /finalized.*?(req\d{6})/i,
44
+ ];
45
+ for (const pattern of patterns) {
46
+ const match = text.match(pattern);
47
+ if (match)
48
+ return match[1];
49
+ }
50
+ return null;
51
+ }
52
+ /**
53
+ * Extract task ID from tool output
54
+ * Matches patterns like "Task tas002152 has been marked as completed"
55
+ *
56
+ * @param text - The tool output text to search
57
+ * @returns The task ID (e.g., "tas002152") or null if not found
58
+ */
59
+ function extractTaskId(text) {
60
+ const match = text.match(/Task\s+(tas\d+).*(?:completed|marked as completed)/i);
61
+ return match ? match[1] : null;
62
+ }
63
+ /**
64
+ * Extract file path from write_file tool output
65
+ * Matches patterns like "Created file path/to/file.ts"
66
+ *
67
+ * @param text - The tool output text to search
68
+ * @returns The file path or null if not found
69
+ */
70
+ function extractFilePath(text) {
71
+ // Match "Created file X" or "**Created file X**"
72
+ const createdMatch = text.match(/\*?\*?Created file\s+([^\s*]+)/i);
73
+ if (createdMatch)
74
+ return createdMatch[1];
75
+ // Match "File Modified: Yes" with path context
76
+ const modifiedMatch = text.match(/File \d+:\s*([^\n]+)\n.*File Modified:\s*Yes/i);
77
+ if (modifiedMatch)
78
+ return modifiedMatch[1];
79
+ return null;
80
+ }
81
+ /**
82
+ * Sanitize a value for use in template interpolation
83
+ * Truncates long values and escapes special characters
84
+ *
85
+ * @param value - The value to sanitize
86
+ * @param maxLength - Maximum length before truncation (default: 1000)
87
+ * @returns The sanitized string
88
+ */
89
+ function sanitizeTemplateValue(value, maxLength = 1000) {
90
+ if (!value)
91
+ return '';
92
+ if (value.length > maxLength) {
93
+ return value.substring(0, maxLength) + '... (truncated)';
94
+ }
95
+ return value;
96
+ }
97
+ //# sourceMappingURL=hook-utils.js.map