specweave 1.0.43 → 1.0.46

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.
package/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
- <!-- SW:META template="claude" version="1.0.41" sections="header,start,autodetect,metarule,rules,workflow,structure,taskformat,secrets,syncing,mapping,testing,limits,troubleshooting,principles,linking,docs" -->
1
+ <!-- SW:META template="claude" version="1.0.44" sections="header,start,autodetect,metarule,rules,workflow,structure,taskformat,secrets,syncing,mapping,testing,limits,troubleshooting,principles,linking,docs" -->
2
2
 
3
- <!-- SW:SECTION:header version="1.0.41" -->
3
+ <!-- SW:SECTION:header version="1.0.44" -->
4
4
  **Framework**: SpecWeave | **Truth**: `spec.md` + `tasks.md`
5
5
  <!-- SW:END:header -->
6
6
 
7
- <!-- SW:SECTION:start version="1.0.41" -->
7
+ <!-- SW:SECTION:start version="1.0.44" -->
8
8
  ## Getting Started
9
9
 
10
10
  **Initial increment**: `0001-project-setup` (auto-created by `specweave init`)
@@ -14,7 +14,7 @@
14
14
  2. **Customize**: Edit spec.md and use for setup tasks
15
15
  <!-- SW:END:start -->
16
16
 
17
- <!-- SW:SECTION:autodetect version="1.0.41" -->
17
+ <!-- SW:SECTION:autodetect version="1.0.44" -->
18
18
  ## Auto-Detection
19
19
 
20
20
  SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
@@ -24,7 +24,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
24
24
  **Opt-out phrases**: "Just brainstorm first" | "Don't plan yet" | "Quick discussion" | "Let's explore ideas"
25
25
  <!-- SW:END:autodetect -->
26
26
 
27
- <!-- SW:SECTION:metarule version="1.0.41" -->
27
+ <!-- SW:SECTION:metarule version="1.0.44" -->
28
28
  ## Meta-Rule: Think-Before-Act
29
29
 
30
30
  **Satisfy dependencies BEFORE dependent operations.**
@@ -35,7 +35,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
35
35
  ```
36
36
  <!-- SW:END:metarule -->
37
37
 
38
- <!-- SW:SECTION:rules version="1.0.41" -->
38
+ <!-- SW:SECTION:rules version="1.0.44" -->
39
39
  ## Rules
40
40
 
41
41
  1. **Files** → `.specweave/increments/####-name/` (spec.md, plan.md, tasks.md at root; reports/, scripts/, logs/ subfolders)
@@ -45,7 +45,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
45
45
  5. **Root clean**: NEVER create .md/reports/scripts in project root → use increment folders
46
46
  <!-- SW:END:rules -->
47
47
 
48
- <!-- SW:SECTION:workflow version="1.0.41" -->
48
+ <!-- SW:SECTION:workflow version="1.0.44" -->
49
49
  ## Workflow
50
50
 
51
51
  `/sw:increment "X"` → `/sw:do` → `/sw:progress` → `/sw:done 0001`
@@ -62,7 +62,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
62
62
  **Natural language**: "Let's build X" → `/sw:increment` | "What's status?" → `/sw:progress` | "We're done" → `/sw:done`
63
63
  <!-- SW:END:workflow -->
64
64
 
65
- <!-- SW:SECTION:structure version="1.0.41" -->
65
+ <!-- SW:SECTION:structure version="1.0.44" -->
66
66
  ## Structure
67
67
 
68
68
  ```
@@ -79,12 +79,12 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
79
79
 
80
80
  **Multi-repo permissions**: In `.claude/settings.json`:
81
81
  ```json
82
- {"permissions":{"allow":["Write(**)","Edit(//**)"],"defaultMode":"bypassPermissions"}}
82
+ {"permissions":{"allow":["Write(//**)","Edit(//**)"],"additionalDirectories":["repositories"],"defaultMode":"bypassPermissions"}}
83
83
  ```
84
- **Path syntax**: `//` = absolute path | `/` = relative to settings file | `**` = recursive
84
+ **Path syntax**: `//path` = absolute | `/path` = relative to settings file | `**` = recursive | `additionalDirectories` = explicit working dirs
85
85
  <!-- SW:END:structure -->
86
86
 
87
- <!-- SW:SECTION:taskformat version="1.0.41" -->
87
+ <!-- SW:SECTION:taskformat version="1.0.44" -->
88
88
  ## Task Format
89
89
 
90
90
  ```markdown
@@ -94,7 +94,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
94
94
  ```
95
95
  <!-- SW:END:taskformat -->
96
96
 
97
- <!-- SW:SECTION:secrets version="1.0.41" -->
97
+ <!-- SW:SECTION:secrets version="1.0.44" -->
98
98
  ## Secrets Check
99
99
 
100
100
  **BEFORE CLI tools**: Check existing config first!
@@ -105,7 +105,7 @@ gh auth status
105
105
  ```
106
106
  <!-- SW:END:secrets -->
107
107
 
108
- <!-- SW:SECTION:syncing version="1.0.41" -->
108
+ <!-- SW:SECTION:syncing version="1.0.44" -->
109
109
  ## External Sync (GitHub/JIRA/ADO)
110
110
 
111
111
  **After increment creation**: Run `/sw-github:sync {id}` to create issues!
@@ -124,7 +124,7 @@ Living docs sync ≠ External sync. They are separate:
124
124
  **Verify tokens**: `grep GITHUB_TOKEN .env` | `gh auth status`
125
125
  <!-- SW:END:syncing -->
126
126
 
127
- <!-- SW:SECTION:mapping version="1.0.41" -->
127
+ <!-- SW:SECTION:mapping version="1.0.44" -->
128
128
  ## GitHub Mapping
129
129
 
130
130
  | SpecWeave | GitHub |
@@ -134,7 +134,7 @@ Living docs sync ≠ External sync. They are separate:
134
134
  | Task T-XXX | Checkbox |
135
135
  <!-- SW:END:mapping -->
136
136
 
137
- <!-- SW:SECTION:testing version="1.0.41" -->
137
+ <!-- SW:SECTION:testing version="1.0.44" -->
138
138
  ## Testing
139
139
 
140
140
  BDD in tasks.md | Unit >80% | `.test.ts` (Vitest)
@@ -146,13 +146,13 @@ vi.mock('fs', () => ({ readFile: vi.fn() }));
146
146
  ```
147
147
  <!-- SW:END:testing -->
148
148
 
149
- <!-- SW:SECTION:limits version="1.0.41" -->
149
+ <!-- SW:SECTION:limits version="1.0.44" -->
150
150
  ## Limits
151
151
 
152
152
  **Max 1500 lines/file** — extract before adding
153
153
  <!-- SW:END:limits -->
154
154
 
155
- <!-- SW:SECTION:troubleshooting version="1.0.41" -->
155
+ <!-- SW:SECTION:troubleshooting version="1.0.44" -->
156
156
  ## Troubleshooting
157
157
 
158
158
  | Issue | Fix |
@@ -166,11 +166,11 @@ vi.mock('fs', () => ({ readFile: vi.fn() }));
166
166
  | GitHub not syncing | Run `/sw-github:sync {id}` explicitly |
167
167
  | Permission denied | Set `canUpsertInternalItems: true` in config.json |
168
168
  | No GITHUB_TOKEN | Check `.env` file or run `gh auth login` |
169
- | Edits blocked in repositories/ | Add `Write(**)`, `Edit(//**)` to `.claude/settings.json` |
170
- | Path patterns not working | Claude Code: `//path` = absolute, `/path` = relative to settings file |
169
+ | Edits blocked in repositories/ | Add `"additionalDirectories":["repositories"]` + `Write(//**)`, `Edit(//**)` to `.claude/settings.json` |
170
+ | Path patterns not working | `//path` = absolute, `/path` = relative to settings file, `additionalDirectories` for explicit working dirs |
171
171
  <!-- SW:END:troubleshooting -->
172
172
 
173
- <!-- SW:SECTION:principles version="1.0.41" -->
173
+ <!-- SW:SECTION:principles version="1.0.44" -->
174
174
  ## Principles
175
175
 
176
176
  1. **Spec-first**: `/sw:increment` before coding
@@ -180,7 +180,7 @@ vi.mock('fs', () => ({ readFile: vi.fn() }));
180
180
  5. **Clean**: All files in increment folders
181
181
  <!-- SW:END:principles -->
182
182
 
183
- <!-- SW:SECTION:linking version="1.0.41" -->
183
+ <!-- SW:SECTION:linking version="1.0.44" -->
184
184
  ## Bidirectional Linking
185
185
 
186
186
  Tasks ↔ User Stories auto-linked via AC-IDs: `AC-US1-01` → `US-001`
@@ -188,7 +188,7 @@ Tasks ↔ User Stories auto-linked via AC-IDs: `AC-US1-01` → `US-001`
188
188
  Task format: `**AC**: AC-US1-01, AC-US1-02` (CRITICAL for linking)
189
189
  <!-- SW:END:linking -->
190
190
 
191
- <!-- SW:SECTION:docs version="1.0.41" -->
191
+ <!-- SW:SECTION:docs version="1.0.44" -->
192
192
  ## Docs
193
193
 
194
194
  [spec-weave.com](https://spec-weave.com) | `.specweave/docs/internal/`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave",
3
- "version": "1.0.43",
3
+ "version": "1.0.46",
4
4
  "description": "Spec-driven development framework for Claude Code. AI-native workflow with living documentation, intelligent agents, and multilingual support (9 languages). Enterprise-grade traceability with permanent specs and temporary increments.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,400 @@
1
+ #!/bin/bash
2
+ # hook-errors.sh - Centralized hook error logging and warning output
3
+ #
4
+ # PRINCIPLE: Hooks MUST NEVER block operations. All errors become warnings.
5
+ #
6
+ # Usage:
7
+ # source "$HOOK_DIR/lib/hook-errors.sh"
8
+ # log_hook_warning "task-ac-sync" "Pattern not found in spec.md"
9
+ # log_hook_error "metadata-guard" "JSON parse failed" "$details"
10
+ #
11
+ # Environment:
12
+ # SPECWEAVE_HOOK_VERBOSE=1 - Show all warnings to user
13
+ # SPECWEAVE_HOOK_DEBUG=1 - Log debug info to files
14
+ #
15
+ # All functions are non-blocking and always return 0
16
+
17
+ set +e
18
+
19
+ # ============================================================================
20
+ # Configuration
21
+ # ============================================================================
22
+
23
+ HOOK_ERRORS_VERSION="1.0.0"
24
+
25
+ # Find project root
26
+ _find_hook_project_root() {
27
+ local dir="${1:-$PWD}"
28
+ while [[ "$dir" != "/" ]]; do
29
+ if [[ -d "$dir/.specweave" ]]; then
30
+ echo "$dir"
31
+ return 0
32
+ fi
33
+ dir=$(dirname "$dir")
34
+ done
35
+ echo "$PWD"
36
+ }
37
+
38
+ _HOOK_PROJECT_ROOT=$(_find_hook_project_root)
39
+ _HOOK_LOGS_DIR="$_HOOK_PROJECT_ROOT/.specweave/logs"
40
+ _HOOK_STATE_DIR="$_HOOK_PROJECT_ROOT/.specweave/state"
41
+
42
+ # Ensure directories exist
43
+ mkdir -p "$_HOOK_LOGS_DIR" 2>/dev/null || true
44
+ mkdir -p "$_HOOK_STATE_DIR" 2>/dev/null || true
45
+
46
+ # Log files
47
+ _HOOK_ERROR_LOG="$_HOOK_LOGS_DIR/hook-errors.log"
48
+ _HOOK_WARNING_LOG="$_HOOK_LOGS_DIR/hook-warnings.log"
49
+ _HOOK_DEBUG_LOG="$_HOOK_LOGS_DIR/hook-debug.log"
50
+
51
+ # ============================================================================
52
+ # Internal Helpers
53
+ # ============================================================================
54
+
55
+ _timestamp() {
56
+ date '+%Y-%m-%d %H:%M:%S'
57
+ }
58
+
59
+ _rotate_log_if_needed() {
60
+ local log_file="$1"
61
+ local max_size="${2:-1048576}" # 1MB default
62
+
63
+ if [[ -f "$log_file" ]]; then
64
+ local size=$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file" 2>/dev/null || echo 0)
65
+ if [[ "$size" -gt "$max_size" ]]; then
66
+ mv "$log_file" "${log_file}.old" 2>/dev/null || true
67
+ fi
68
+ fi
69
+ }
70
+
71
+ # ============================================================================
72
+ # Public API: Logging Functions
73
+ # ============================================================================
74
+
75
+ # Log a warning - operation continues, user sees message if VERBOSE=1
76
+ # Usage: log_hook_warning "hook-name" "message"
77
+ log_hook_warning() {
78
+ local hook_name="${1:-unknown}"
79
+ local message="${2:-No message}"
80
+ local timestamp=$(_timestamp)
81
+
82
+ # Always log to file
83
+ echo "[$timestamp] WARNING [$hook_name]: $message" >> "$_HOOK_WARNING_LOG" 2>/dev/null || true
84
+
85
+ # Show to user if verbose mode
86
+ if [[ "${SPECWEAVE_HOOK_VERBOSE:-0}" == "1" ]]; then
87
+ echo ""
88
+ echo " WARNING [$hook_name]: $message"
89
+ echo ""
90
+ fi
91
+
92
+ return 0
93
+ }
94
+
95
+ # Log an error - operation continues, but this is significant
96
+ # Usage: log_hook_error "hook-name" "message" ["details"]
97
+ log_hook_error() {
98
+ local hook_name="${1:-unknown}"
99
+ local message="${2:-No message}"
100
+ local details="${3:-}"
101
+ local timestamp=$(_timestamp)
102
+
103
+ # Rotate log if too large
104
+ _rotate_log_if_needed "$_HOOK_ERROR_LOG"
105
+
106
+ # Always log to file with details
107
+ {
108
+ echo "============================================================"
109
+ echo "[$timestamp] ERROR [$hook_name]"
110
+ echo "Message: $message"
111
+ if [[ -n "$details" ]]; then
112
+ echo "Details: $details"
113
+ fi
114
+ echo "============================================================"
115
+ } >> "$_HOOK_ERROR_LOG" 2>/dev/null || true
116
+
117
+ # Always show errors to user (they're significant)
118
+ echo ""
119
+ echo " [Hook Warning] $hook_name: $message"
120
+ if [[ -n "$details" ]] && [[ "${SPECWEAVE_HOOK_DEBUG:-0}" == "1" ]]; then
121
+ echo " Details: $details"
122
+ fi
123
+ echo " (Operation continues - hook errors are non-blocking)"
124
+ echo ""
125
+
126
+ return 0
127
+ }
128
+
129
+ # Log debug info - only to file if DEBUG=1
130
+ # Usage: log_hook_debug "hook-name" "message"
131
+ log_hook_debug() {
132
+ local hook_name="${1:-unknown}"
133
+ local message="${2:-}"
134
+
135
+ if [[ "${SPECWEAVE_HOOK_DEBUG:-0}" == "1" ]]; then
136
+ local timestamp=$(_timestamp)
137
+ _rotate_log_if_needed "$_HOOK_DEBUG_LOG"
138
+ echo "[$timestamp] DEBUG [$hook_name]: $message" >> "$_HOOK_DEBUG_LOG" 2>/dev/null || true
139
+ fi
140
+
141
+ return 0
142
+ }
143
+
144
+ # Log hook start - for tracking which hooks ran
145
+ # Usage: log_hook_start "hook-name" ["context"]
146
+ log_hook_start() {
147
+ local hook_name="${1:-unknown}"
148
+ local context="${2:-}"
149
+
150
+ log_hook_debug "$hook_name" "START ${context:+- $context}"
151
+ return 0
152
+ }
153
+
154
+ # Log hook end - with success/failure status
155
+ # Usage: log_hook_end "hook-name" "success|failure" ["message"]
156
+ log_hook_end() {
157
+ local hook_name="${1:-unknown}"
158
+ local status="${2:-success}"
159
+ local message="${3:-}"
160
+
161
+ log_hook_debug "$hook_name" "END ($status) ${message:+- $message}"
162
+ return 0
163
+ }
164
+
165
+ # ============================================================================
166
+ # Public API: Safe Execution Helpers
167
+ # ============================================================================
168
+
169
+ # Run a command safely, logging errors but never failing
170
+ # Usage: safe_run "hook-name" command arg1 arg2...
171
+ safe_run() {
172
+ local hook_name="${1:-unknown}"
173
+ shift
174
+
175
+ local output
176
+ local exit_code
177
+
178
+ output=$("$@" 2>&1)
179
+ exit_code=$?
180
+
181
+ if [[ $exit_code -ne 0 ]]; then
182
+ log_hook_warning "$hook_name" "Command failed (exit $exit_code): $*"
183
+ log_hook_debug "$hook_name" "Output: $output"
184
+ fi
185
+
186
+ return 0 # Always succeed
187
+ }
188
+
189
+ # Run a file modification safely with backup
190
+ # Usage: safe_file_modify "hook-name" "file" "command with \$file placeholder"
191
+ safe_file_modify() {
192
+ local hook_name="${1:-unknown}"
193
+ local file="$2"
194
+ local command="$3"
195
+
196
+ if [[ ! -f "$file" ]]; then
197
+ log_hook_warning "$hook_name" "File not found: $file"
198
+ return 0
199
+ fi
200
+
201
+ # Create backup
202
+ local backup="${file}.hook-backup"
203
+ cp "$file" "$backup" 2>/dev/null || {
204
+ log_hook_warning "$hook_name" "Failed to create backup of $file"
205
+ return 0
206
+ }
207
+
208
+ # Run command (replace $file placeholder)
209
+ local actual_command="${command//\$file/$file}"
210
+ if ! eval "$actual_command" 2>/dev/null; then
211
+ log_hook_warning "$hook_name" "Modification failed, restoring backup"
212
+ mv "$backup" "$file" 2>/dev/null || true
213
+ return 0
214
+ fi
215
+
216
+ # Verify file is valid (not empty, not truncated)
217
+ if [[ ! -s "$file" ]]; then
218
+ log_hook_error "$hook_name" "File became empty after modification, restoring"
219
+ mv "$backup" "$file" 2>/dev/null || true
220
+ return 0
221
+ fi
222
+
223
+ # Success - remove backup
224
+ rm -f "$backup" 2>/dev/null || true
225
+ return 0
226
+ }
227
+
228
+ # ============================================================================
229
+ # Public API: Pattern Matching Helpers
230
+ # ============================================================================
231
+
232
+ # Check if a pattern exists in file (with logging)
233
+ # Usage: if pattern_exists "hook-name" "file" "pattern"; then ...
234
+ pattern_exists() {
235
+ local hook_name="${1:-unknown}"
236
+ local file="$2"
237
+ local pattern="$3"
238
+
239
+ if [[ ! -f "$file" ]]; then
240
+ log_hook_debug "$hook_name" "pattern_exists: file not found: $file"
241
+ return 1
242
+ fi
243
+
244
+ if grep -qE "$pattern" "$file" 2>/dev/null; then
245
+ log_hook_debug "$hook_name" "pattern_exists: found '$pattern' in $file"
246
+ return 0
247
+ else
248
+ log_hook_debug "$hook_name" "pattern_exists: NOT found '$pattern' in $file"
249
+ return 1
250
+ fi
251
+ }
252
+
253
+ # Safe sed replacement with pattern existence check
254
+ # Usage: safe_sed_replace "hook-name" "file" "pattern" "replacement"
255
+ safe_sed_replace() {
256
+ local hook_name="${1:-unknown}"
257
+ local file="$2"
258
+ local pattern="$3"
259
+ local replacement="$4"
260
+
261
+ if [[ ! -f "$file" ]]; then
262
+ log_hook_warning "$hook_name" "Cannot replace: file not found: $file"
263
+ return 0
264
+ fi
265
+
266
+ # Check if pattern exists first
267
+ if ! grep -qE "$pattern" "$file" 2>/dev/null; then
268
+ log_hook_debug "$hook_name" "Pattern not found (may already be updated): $pattern"
269
+ return 0 # Not an error - pattern may have been updated already
270
+ fi
271
+
272
+ # Try macOS sed first, then Linux
273
+ if sed -i '' "s|$pattern|$replacement|g" "$file" 2>/dev/null; then
274
+ log_hook_debug "$hook_name" "Replaced pattern (macOS sed)"
275
+ return 0
276
+ elif sed -i "s|$pattern|$replacement|g" "$file" 2>/dev/null; then
277
+ log_hook_debug "$hook_name" "Replaced pattern (Linux sed)"
278
+ return 0
279
+ else
280
+ log_hook_warning "$hook_name" "sed replacement failed for pattern: $pattern"
281
+ return 0 # Still don't fail
282
+ fi
283
+ }
284
+
285
+ # ============================================================================
286
+ # Public API: JSON Helpers
287
+ # ============================================================================
288
+
289
+ # Safe JSON read with fallback
290
+ # Usage: value=$(safe_json_get "hook-name" "file.json" ".key")
291
+ safe_json_get() {
292
+ local hook_name="${1:-unknown}"
293
+ local file="$2"
294
+ local key="$3"
295
+ local default="${4:-}"
296
+
297
+ if [[ ! -f "$file" ]]; then
298
+ echo "$default"
299
+ return 0
300
+ fi
301
+
302
+ if ! command -v jq >/dev/null 2>&1; then
303
+ log_hook_debug "$hook_name" "jq not available, returning default"
304
+ echo "$default"
305
+ return 0
306
+ fi
307
+
308
+ local value
309
+ value=$(jq -r "$key // empty" "$file" 2>/dev/null)
310
+
311
+ if [[ -z "$value" ]] || [[ "$value" == "null" ]]; then
312
+ echo "$default"
313
+ else
314
+ echo "$value"
315
+ fi
316
+
317
+ return 0
318
+ }
319
+
320
+ # Safe JSON update
321
+ # Usage: safe_json_set "hook-name" "file.json" ".key" "value"
322
+ safe_json_set() {
323
+ local hook_name="${1:-unknown}"
324
+ local file="$2"
325
+ local key="$3"
326
+ local value="$4"
327
+
328
+ if [[ ! -f "$file" ]]; then
329
+ log_hook_warning "$hook_name" "Cannot update JSON: file not found: $file"
330
+ return 0
331
+ fi
332
+
333
+ if ! command -v jq >/dev/null 2>&1; then
334
+ log_hook_warning "$hook_name" "jq not available, skipping JSON update"
335
+ return 0
336
+ fi
337
+
338
+ local tmp_file
339
+ tmp_file=$(mktemp)
340
+
341
+ if jq "$key = $value" "$file" > "$tmp_file" 2>/dev/null && [[ -s "$tmp_file" ]]; then
342
+ mv "$tmp_file" "$file" 2>/dev/null || {
343
+ log_hook_warning "$hook_name" "Failed to write JSON update"
344
+ rm -f "$tmp_file"
345
+ }
346
+ else
347
+ log_hook_warning "$hook_name" "jq update failed for $key"
348
+ rm -f "$tmp_file"
349
+ fi
350
+
351
+ return 0
352
+ }
353
+
354
+ # ============================================================================
355
+ # Public API: Output Helpers
356
+ # ============================================================================
357
+
358
+ # Output a visible warning banner to user
359
+ # Usage: show_hook_warning_banner "title" "message line 1" "message line 2"
360
+ show_hook_warning_banner() {
361
+ local title="$1"
362
+ shift
363
+
364
+ echo ""
365
+ echo " WARNING: $title"
366
+ echo " ----------------------------------------"
367
+ for line in "$@"; do
368
+ echo " $line"
369
+ done
370
+ echo " ----------------------------------------"
371
+ echo ""
372
+ }
373
+
374
+ # Output a non-blocking info message
375
+ # Usage: show_hook_info "Operation completed with warnings"
376
+ show_hook_info() {
377
+ local message="$1"
378
+ echo ""
379
+ echo " [Info] $message"
380
+ echo ""
381
+ }
382
+
383
+ # ============================================================================
384
+ # Initialization
385
+ # ============================================================================
386
+
387
+ # Export all functions for use in subshells
388
+ export -f log_hook_warning 2>/dev/null || true
389
+ export -f log_hook_error 2>/dev/null || true
390
+ export -f log_hook_debug 2>/dev/null || true
391
+ export -f log_hook_start 2>/dev/null || true
392
+ export -f log_hook_end 2>/dev/null || true
393
+ export -f safe_run 2>/dev/null || true
394
+ export -f safe_file_modify 2>/dev/null || true
395
+ export -f pattern_exists 2>/dev/null || true
396
+ export -f safe_sed_replace 2>/dev/null || true
397
+ export -f safe_json_get 2>/dev/null || true
398
+ export -f safe_json_set 2>/dev/null || true
399
+ export -f show_hook_warning_banner 2>/dev/null || true
400
+ export -f show_hook_info 2>/dev/null || true