specweave 1.0.31 → 1.0.33

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 (123) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CLAUDE.md +205 -148
  3. package/README.md +0 -2
  4. package/bin/specweave.js +11 -0
  5. package/dist/src/cli/commands/init.js +1 -1
  6. package/dist/src/cli/commands/init.js.map +1 -1
  7. package/dist/src/cli/commands/update-instructions.d.ts +16 -0
  8. package/dist/src/cli/commands/update-instructions.d.ts.map +1 -0
  9. package/dist/src/cli/commands/update-instructions.js +134 -0
  10. package/dist/src/cli/commands/update-instructions.js.map +1 -0
  11. package/dist/src/cli/helpers/init/directory-structure.d.ts +28 -1
  12. package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
  13. package/dist/src/cli/helpers/init/directory-structure.js +163 -33
  14. package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
  15. package/dist/src/cli/helpers/init/index.d.ts +2 -1
  16. package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
  17. package/dist/src/cli/helpers/init/index.js +3 -1
  18. package/dist/src/cli/helpers/init/index.js.map +1 -1
  19. package/dist/src/cli/helpers/init/instruction-file-merger.d.ts +23 -0
  20. package/dist/src/cli/helpers/init/instruction-file-merger.d.ts.map +1 -0
  21. package/dist/src/cli/helpers/init/instruction-file-merger.js +243 -0
  22. package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -0
  23. package/dist/src/cli/helpers/init/plugin-installer.js +49 -0
  24. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
  25. package/dist/src/config/types.d.ts +2 -2
  26. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +26 -0
  27. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -1
  28. package/dist/src/core/living-docs/external-sync-orchestrator.js +61 -0
  29. package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -1
  30. package/dist/src/core/living-docs/scaffolding/index.d.ts +12 -0
  31. package/dist/src/core/living-docs/scaffolding/index.d.ts.map +1 -0
  32. package/dist/src/core/living-docs/scaffolding/index.js +15 -0
  33. package/dist/src/core/living-docs/scaffolding/index.js.map +1 -0
  34. package/dist/src/core/living-docs/scaffolding/merger.d.ts +183 -0
  35. package/dist/src/core/living-docs/scaffolding/merger.d.ts.map +1 -0
  36. package/dist/src/core/living-docs/scaffolding/merger.js +523 -0
  37. package/dist/src/core/living-docs/scaffolding/merger.js.map +1 -0
  38. package/dist/src/core/living-docs/scaffolding/scaffold.d.ts +102 -0
  39. package/dist/src/core/living-docs/scaffolding/scaffold.d.ts.map +1 -0
  40. package/dist/src/core/living-docs/scaffolding/scaffold.js +346 -0
  41. package/dist/src/core/living-docs/scaffolding/scaffold.js.map +1 -0
  42. package/dist/src/core/living-docs/scaffolding/template-engine.d.ts +108 -0
  43. package/dist/src/core/living-docs/scaffolding/template-engine.d.ts.map +1 -0
  44. package/dist/src/core/living-docs/scaffolding/template-engine.js +204 -0
  45. package/dist/src/core/living-docs/scaffolding/template-engine.js.map +1 -0
  46. package/dist/src/core/living-docs/sync-helpers/generators.d.ts +38 -2
  47. package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
  48. package/dist/src/core/living-docs/sync-helpers/generators.js +65 -10
  49. package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
  50. package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
  51. package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
  52. package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
  53. package/dist/src/core/tools/index.d.ts +11 -0
  54. package/dist/src/core/tools/index.d.ts.map +1 -0
  55. package/dist/src/core/tools/index.js +10 -0
  56. package/dist/src/core/tools/index.js.map +1 -0
  57. package/dist/src/core/tools/tool-event-bus.d.ts +33 -0
  58. package/dist/src/core/tools/tool-event-bus.d.ts.map +1 -0
  59. package/dist/src/core/tools/tool-event-bus.js +84 -0
  60. package/dist/src/core/tools/tool-event-bus.js.map +1 -0
  61. package/dist/src/core/tools/tool-index-builder.d.ts +27 -0
  62. package/dist/src/core/tools/tool-index-builder.d.ts.map +1 -0
  63. package/dist/src/core/tools/tool-index-builder.js +289 -0
  64. package/dist/src/core/tools/tool-index-builder.js.map +1 -0
  65. package/dist/src/core/tools/tool-registry.d.ts +51 -0
  66. package/dist/src/core/tools/tool-registry.d.ts.map +1 -0
  67. package/dist/src/core/tools/tool-registry.js +224 -0
  68. package/dist/src/core/tools/tool-registry.js.map +1 -0
  69. package/dist/src/core/tools/tool-search-engine.d.ts +22 -0
  70. package/dist/src/core/tools/tool-search-engine.d.ts.map +1 -0
  71. package/dist/src/core/tools/tool-search-engine.js +174 -0
  72. package/dist/src/core/tools/tool-search-engine.js.map +1 -0
  73. package/dist/src/core/tools/types/tool-registry-types.d.ts +112 -0
  74. package/dist/src/core/tools/types/tool-registry-types.d.ts.map +1 -0
  75. package/dist/src/core/tools/types/tool-registry-types.js +7 -0
  76. package/dist/src/core/tools/types/tool-registry-types.js.map +1 -0
  77. package/dist/src/init/compliance/types.d.ts +1 -1
  78. package/package.json +1 -1
  79. package/plugins/specweave/hooks/hooks.json +3 -13
  80. package/plugins/specweave/hooks/lib/common-setup.sh +47 -321
  81. package/plugins/specweave/hooks/lib/migrate-increment-work.sh +5 -5
  82. package/plugins/specweave/hooks/lib/sync-spec-content.sh +5 -5
  83. package/plugins/specweave/hooks/universal/dispatcher.mjs +4 -5
  84. package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +43 -296
  85. package/plugins/specweave/hooks/universal/hook-wrapper.sh +3 -1
  86. package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
  87. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +2 -2
  88. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -10
  89. package/plugins/specweave/hooks/v2/guards/completion-guard.sh +12 -29
  90. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +27 -29
  91. package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +10 -4
  92. package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +139 -0
  93. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +4 -2
  94. package/plugins/specweave/hooks/v2/session-end.sh +3 -1
  95. package/plugins/specweave/hooks/v2/session-start.sh +3 -1
  96. package/plugins/specweave/skills/increment-planner/templates/plan.md +14 -0
  97. package/plugins/specweave/skills/update-instructions/SKILL.md +80 -0
  98. package/plugins/specweave-ado/hooks/post-living-docs-update.sh +1 -1
  99. package/plugins/specweave-mobile/README.md +55 -35
  100. package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +805 -329
  101. package/plugins/specweave-mobile/skills/expo-workflow/SKILL.md +226 -9
  102. package/plugins/specweave-mobile/skills/native-modules/SKILL.md +221 -20
  103. package/plugins/specweave-mobile/skills/performance-optimization/SKILL.md +186 -14
  104. package/plugins/specweave-mobile/skills/react-native-setup/SKILL.md +151 -54
  105. package/plugins/specweave-release/commands/npm.md +61 -17
  106. package/plugins/specweave-release/hooks/post-task-completion.sh +2 -3
  107. package/src/templates/AGENTS.md.template +34 -0
  108. package/src/templates/CLAUDE.md.template +121 -155
  109. package/plugins/specweave/hooks/config-env-separator.sh +0 -99
  110. package/plugins/specweave/hooks/github-metadata-guard.sh +0 -73
  111. package/plugins/specweave/hooks/lib/circuit-breaker.sh +0 -381
  112. package/plugins/specweave/hooks/lib/crash-prevention.sh +0 -336
  113. package/plugins/specweave/hooks/lib/logging.sh +0 -231
  114. package/plugins/specweave/hooks/lib/metrics.sh +0 -347
  115. package/plugins/specweave/hooks/lib/semaphore.sh +0 -216
  116. package/plugins/specweave/hooks/project-folder-guard.sh +0 -274
  117. package/plugins/specweave/hooks/spec-project-validator.sh +0 -210
  118. package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +0 -212
  119. package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +0 -163
  120. package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +0 -51
  121. package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +0 -63
  122. package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +0 -335
  123. package/plugins/specweave/hooks/v2/guards/per-us-project-validator.test.sh +0 -406
@@ -1,336 +0,0 @@
1
- #!/bin/bash
2
- # crash-prevention.sh - Unified Crash Prevention Runtime
3
- #
4
- # Consolidates ALL crash prevention patterns from 9 crash categories:
5
- # 1. Bash heredoc hangs (infinite wait for EOF)
6
- # 2. Context explosion (>280KB total)
7
- # 3. Hook recursion loops
8
- # 4. Process storms (bulk operations)
9
- # 5. Agent chunking violations
10
- # 6. Direct completion bypass
11
- # 7. Hook registration duplicates
12
- # 8. Context compaction deadlock
13
- # 9. MCP connection drops
14
- #
15
- # This is the SINGLE SOURCE OF TRUTH for crash prevention.
16
- # Consolidated from ADRs: 0060, 0068, 0073, 0127, 0128, 0130, 0133, 0157, 0189
17
- #
18
- # v0.33.0 - Initial consolidation
19
-
20
- set +e
21
-
22
- # ============================================================================
23
- # CRASH CATEGORY 1: BASH HEREDOC DETECTION
24
- # ============================================================================
25
-
26
- # Patterns that cause INFINITE HANGS (shell waits forever for EOF)
27
- detect_bash_hang_pattern() {
28
- local command="$1"
29
-
30
- # Heredoc patterns (MOST DANGEROUS)
31
- if echo "$command" | grep -qE "<<-?[[:space:]]*['\"]?[A-Za-z_]+" 2>/dev/null; then
32
- echo "heredoc"
33
- return 0
34
- fi
35
-
36
- # Cat stdin redirect (waits forever)
37
- if echo "$command" | grep -qE "^[[:space:]]*cat[[:space:]]+>[[:space:]]*[^>]" 2>/dev/null; then
38
- echo "cat-stdin"
39
- return 0
40
- fi
41
-
42
- # DD command with output file
43
- if echo "$command" | grep -qE "^[[:space:]]*dd[[:space:]].*of=" 2>/dev/null; then
44
- echo "dd-stdin"
45
- return 0
46
- fi
47
-
48
- # Echo/printf to file (truncation risk)
49
- if echo "$command" | grep -qE "^[[:space:]]*(echo|printf)[[:space:]]" 2>/dev/null; then
50
- if echo "$command" | grep -qE '>[[:space:]]*[^>]' 2>/dev/null; then
51
- if ! echo "$command" | grep -qE '>>' 2>/dev/null; then
52
- echo "echo-redirect"
53
- return 0
54
- fi
55
- fi
56
- fi
57
-
58
- return 1
59
- }
60
-
61
- # ============================================================================
62
- # CRASH CATEGORY 2: CONTEXT BUDGET ESTIMATION
63
- # ============================================================================
64
-
65
- # Rough token estimation (1 token ≈ 4 chars)
66
- estimate_tokens() {
67
- local file="$1"
68
- if [[ -f "$file" ]]; then
69
- local chars
70
- chars=$(wc -c < "$file" 2>/dev/null || echo "0")
71
- echo $((chars / 4))
72
- else
73
- echo "0"
74
- fi
75
- }
76
-
77
- # Check if context is in danger zone
78
- check_context_budget() {
79
- local project_root="$1"
80
- local active_increment="$2"
81
-
82
- local total_tokens=0
83
- local warning_threshold=150000 # ~150K tokens
84
- local danger_threshold=200000 # ~200K tokens
85
-
86
- # Estimate increment context
87
- if [[ -n "$active_increment" ]] && [[ -d "$project_root/.specweave/increments/$active_increment" ]]; then
88
- local inc_dir="$project_root/.specweave/increments/$active_increment"
89
-
90
- local spec_tokens=$(estimate_tokens "$inc_dir/spec.md")
91
- local plan_tokens=$(estimate_tokens "$inc_dir/plan.md")
92
- local tasks_tokens=$(estimate_tokens "$inc_dir/tasks.md")
93
-
94
- total_tokens=$((spec_tokens + plan_tokens + tasks_tokens))
95
- fi
96
-
97
- # Return status
98
- if [[ $total_tokens -gt $danger_threshold ]]; then
99
- echo "DANGER:$total_tokens"
100
- return 2
101
- elif [[ $total_tokens -gt $warning_threshold ]]; then
102
- echo "WARNING:$total_tokens"
103
- return 1
104
- else
105
- echo "OK:$total_tokens"
106
- return 0
107
- fi
108
- }
109
-
110
- # Count tasks in active increment
111
- count_increment_tasks() {
112
- local project_root="$1"
113
- local active_increment="$2"
114
-
115
- local tasks_file="$project_root/.specweave/increments/$active_increment/tasks.md"
116
- if [[ -f "$tasks_file" ]]; then
117
- grep -c "^### T-" "$tasks_file" 2>/dev/null || echo "0"
118
- else
119
- echo "0"
120
- fi
121
- }
122
-
123
- # ============================================================================
124
- # CRASH CATEGORY 3: PROCESS STORM DETECTION
125
- # ============================================================================
126
-
127
- # Count running hook processes
128
- count_hook_processes() {
129
- ps aux 2>/dev/null | grep -c "specweave.*hook" || echo "0"
130
- }
131
-
132
- # Detect if we're in a process storm
133
- detect_process_storm() {
134
- local threshold="${1:-20}"
135
- local count
136
- count=$(count_hook_processes)
137
-
138
- if [[ "$count" -gt "$threshold" ]]; then
139
- echo "STORM:$count"
140
- return 1
141
- fi
142
- echo "OK:$count"
143
- return 0
144
- }
145
-
146
- # ============================================================================
147
- # CRASH CATEGORY 4: ZOMBIE PROCESS DETECTION
148
- # ============================================================================
149
-
150
- # Find and kill zombie heredoc processes
151
- kill_zombie_heredocs() {
152
- # Kill any cat processes waiting for EOF
153
- pkill -f "cat.*EOF" 2>/dev/null || true
154
- pkill -f "cat.*<<" 2>/dev/null || true
155
-
156
- # Kill stale bash processes related to specweave hooks
157
- local stale_pids
158
- stale_pids=$(ps aux 2>/dev/null | grep -E "bash.*specweave.*hook" | grep -v grep | awk '{print $2}' | head -10)
159
-
160
- for pid in $stale_pids; do
161
- # Check if process is older than 30 seconds
162
- local elapsed
163
- elapsed=$(ps -o etimes= -p "$pid" 2>/dev/null | tr -d ' ')
164
- if [[ -n "$elapsed" ]] && [[ "$elapsed" -gt 30 ]]; then
165
- kill -9 "$pid" 2>/dev/null || true
166
- fi
167
- done
168
- }
169
-
170
- # ============================================================================
171
- # CRASH CATEGORY 5: LOCK FILE CLEANUP
172
- # ============================================================================
173
-
174
- # Clean stale lock files (older than 60s)
175
- clean_stale_locks() {
176
- local project_root="$1"
177
- local state_dir="$project_root/.specweave/state"
178
-
179
- [[ ! -d "$state_dir" ]] && return
180
-
181
- find "$state_dir" -name "*.lock" -type d -mmin +1 2>/dev/null | while read -r lock; do
182
- rmdir "$lock" 2>/dev/null || true
183
- done
184
-
185
- # Also clean stale guard files
186
- find "$state_dir" -name ".hook-*-guard" -type f -mmin +1 2>/dev/null | while read -r guard; do
187
- rm -f "$guard" 2>/dev/null
188
- done
189
- }
190
-
191
- # ============================================================================
192
- # CRASH CATEGORY 6: SESSION HEALTH CHECK
193
- # ============================================================================
194
-
195
- # Comprehensive health check
196
- check_session_health() {
197
- local project_root="$1"
198
- local issues=()
199
-
200
- # Check 1: Process storm
201
- local storm_status
202
- storm_status=$(detect_process_storm 15)
203
- if [[ "$storm_status" == STORM* ]]; then
204
- issues+=("Process storm detected: $storm_status")
205
- fi
206
-
207
- # Check 2: Stale locks
208
- local stale_locks
209
- stale_locks=$(find "$project_root/.specweave/state" -name "*.lock" -type d -mmin +1 2>/dev/null | wc -l | tr -d ' ')
210
- if [[ "$stale_locks" -gt 0 ]]; then
211
- issues+=("Stale locks: $stale_locks")
212
- fi
213
-
214
- # Check 3: Circuit breaker status
215
- local cb_file="$project_root/.specweave/state/.hook-circuit-breaker"
216
- if [[ -f "$cb_file" ]]; then
217
- local failures
218
- failures=$(cat "$cb_file" 2>/dev/null || echo "0")
219
- if [[ "$failures" -ge 3 ]]; then
220
- issues+=("Circuit breaker OPEN: $failures failures")
221
- fi
222
- fi
223
-
224
- # Check 4: Active increment task count
225
- local active_inc
226
- active_inc=$(find "$project_root/.specweave/increments" -maxdepth 1 -type d -name "[0-9]*" 2>/dev/null | head -1 | xargs basename 2>/dev/null)
227
- if [[ -n "$active_inc" ]]; then
228
- local task_count
229
- task_count=$(count_increment_tasks "$project_root" "$active_inc")
230
- if [[ "$task_count" -gt 8 ]]; then
231
- issues+=("Task count exceeds limit: $task_count/8 in $active_inc")
232
- fi
233
- fi
234
-
235
- # Report
236
- if [[ ${#issues[@]} -eq 0 ]]; then
237
- echo "HEALTHY"
238
- return 0
239
- else
240
- echo "ISSUES:"
241
- printf '%s\n' "${issues[@]}"
242
- return 1
243
- fi
244
- }
245
-
246
- # ============================================================================
247
- # CRASH CATEGORY 7: EMERGENCY CLEANUP
248
- # ============================================================================
249
-
250
- # Nuclear option: clean ALL state
251
- emergency_cleanup() {
252
- local project_root="$1"
253
-
254
- echo "=== EMERGENCY CLEANUP ==="
255
-
256
- # 1. Kill zombie processes
257
- echo "1. Killing zombie processes..."
258
- kill_zombie_heredocs
259
- pkill -9 -f "bash.*specweave" 2>/dev/null || true
260
-
261
- # 2. Remove all locks
262
- echo "2. Removing all locks..."
263
- rm -rf "$project_root/.specweave/state/"*.lock 2>/dev/null
264
- rm -f "$project_root/.specweave/state/.hook-"* 2>/dev/null
265
- rm -f "$project_root/.specweave/state/.processor.lock" 2>/dev/null
266
-
267
- # 3. Reset circuit breakers
268
- echo "3. Resetting circuit breakers..."
269
- rm -f "$project_root/.specweave/state/.hook-circuit-breaker"* 2>/dev/null
270
-
271
- # 4. Clear dedup cache
272
- echo "4. Clearing dedup cache..."
273
- rm -rf "$project_root/.specweave/state/.dedup-cache" 2>/dev/null
274
-
275
- # 5. Clear bulk operation counter
276
- echo "5. Clearing bulk operation counter..."
277
- rm -f "$project_root/.specweave/state/.bulk-op-counter" 2>/dev/null
278
-
279
- echo "=== CLEANUP COMPLETE ==="
280
- echo "Restart Claude Code to continue."
281
- }
282
-
283
- # ============================================================================
284
- # RUNTIME GUARDS (for use in hooks)
285
- # ============================================================================
286
-
287
- # Pre-execution guard for any hook
288
- guard_hook_execution() {
289
- local hook_name="$1"
290
- local project_root="$2"
291
-
292
- # Check for process storm
293
- local storm
294
- storm=$(detect_process_storm 20)
295
- if [[ "$storm" == STORM* ]]; then
296
- echo "[GUARD] Blocking $hook_name: $storm" >&2
297
- return 1
298
- fi
299
-
300
- return 0
301
- }
302
-
303
- # ============================================================================
304
- # MAIN (when run directly)
305
- # ============================================================================
306
-
307
- if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
308
- case "${1:-health}" in
309
- health)
310
- PROJECT_ROOT=$(find "$PWD" -maxdepth 3 -type d -name ".specweave" 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
311
- if [[ -n "$PROJECT_ROOT" ]]; then
312
- check_session_health "$PROJECT_ROOT"
313
- else
314
- echo "Not in a SpecWeave project"
315
- exit 1
316
- fi
317
- ;;
318
- cleanup)
319
- PROJECT_ROOT=$(find "$PWD" -maxdepth 3 -type d -name ".specweave" 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
320
- if [[ -n "$PROJECT_ROOT" ]]; then
321
- emergency_cleanup "$PROJECT_ROOT"
322
- else
323
- echo "Not in a SpecWeave project"
324
- exit 1
325
- fi
326
- ;;
327
- kill-zombies)
328
- kill_zombie_heredocs
329
- echo "Zombie processes cleaned"
330
- ;;
331
- *)
332
- echo "Usage: $0 {health|cleanup|kill-zombies}"
333
- exit 1
334
- ;;
335
- esac
336
- fi
@@ -1,231 +0,0 @@
1
- #!/bin/bash
2
- # logging.sh - Structured logging for SpecWeave hooks
3
- #
4
- # FEATURES:
5
- # - Structured JSON logs for machine parsing
6
- # - Human-readable console output
7
- # - Request ID tracing across hook chains
8
- # - Log levels (DEBUG, INFO, WARN, ERROR)
9
- # - Automatic log rotation
10
- # - Performance timing
11
- #
12
- # USAGE:
13
- # source logging.sh
14
- # log_init "hook-name"
15
- # log_info "Processing request"
16
- # log_error "Something failed" "error_code=123"
17
- # log_timing_start "operation"
18
- # # ... do work ...
19
- # log_timing_end "operation"
20
- #
21
- # v1.0.0 - Initial implementation (2025-12-17)
22
-
23
- # === Configuration ===
24
- LOG_DIR="${SPECWEAVE_LOG_DIR:-.specweave/logs/hooks}"
25
- LOG_LEVEL="${LOG_LEVEL:-INFO}" # DEBUG, INFO, WARN, ERROR
26
- LOG_FORMAT="${LOG_FORMAT:-json}" # json, text
27
- LOG_MAX_SIZE_KB="${LOG_MAX_SIZE_KB:-1024}" # 1MB per file
28
- LOG_ROTATION_COUNT="${LOG_ROTATION_COUNT:-5}"
29
-
30
- # Log level numeric values
31
- declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
32
-
33
- # Request context
34
- _LOG_REQUEST_ID=""
35
- _LOG_HOOK_NAME=""
36
- _LOG_START_TIME=""
37
- declare -A _LOG_TIMINGS
38
-
39
- # === Initialization ===
40
- log_init() {
41
- local hook_name="$1"
42
-
43
- mkdir -p "$LOG_DIR" 2>/dev/null || true
44
-
45
- _LOG_HOOK_NAME="$hook_name"
46
- _LOG_REQUEST_ID="${REQUEST_ID:-$(generate_request_id)}"
47
- _LOG_START_TIME=$(get_timestamp_ms)
48
-
49
- export REQUEST_ID="$_LOG_REQUEST_ID"
50
- }
51
-
52
- # === Request ID Generation ===
53
- generate_request_id() {
54
- # Format: HHMMSS-RANDOM (compact but unique enough for tracing)
55
- local time_part
56
- time_part=$(date +%H%M%S)
57
- local random_part
58
- random_part=$(printf '%04x' $((RANDOM % 65536)))
59
- echo "${time_part}-${random_part}"
60
- }
61
-
62
- # === Timestamp ===
63
- get_timestamp_ms() {
64
- if command -v gdate &>/dev/null; then
65
- gdate +%s%3N
66
- elif date +%s%N &>/dev/null 2>&1; then
67
- echo $(($(date +%s%N) / 1000000))
68
- else
69
- echo "$(($(date +%s) * 1000))"
70
- fi
71
- }
72
-
73
- get_timestamp_iso() {
74
- date -u +"%Y-%m-%dT%H:%M:%S.000Z" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ"
75
- }
76
-
77
- # === Level Check ===
78
- _should_log() {
79
- local level="$1"
80
- local level_num="${LOG_LEVELS[$level]:-1}"
81
- local threshold_num="${LOG_LEVELS[$LOG_LEVEL]:-1}"
82
- [[ "$level_num" -ge "$threshold_num" ]]
83
- }
84
-
85
- # === Log Rotation ===
86
- _rotate_log_if_needed() {
87
- local log_file="$1"
88
-
89
- [[ ! -f "$log_file" ]] && return
90
-
91
- local size_kb
92
- if [[ "$(uname)" == "Darwin" ]]; then
93
- size_kb=$(($(stat -f %z "$log_file" 2>/dev/null || echo 0) / 1024))
94
- else
95
- size_kb=$(($(stat -c %s "$log_file" 2>/dev/null || echo 0) / 1024))
96
- fi
97
-
98
- if [[ "$size_kb" -gt "$LOG_MAX_SIZE_KB" ]]; then
99
- # Rotate logs
100
- for i in $(seq $((LOG_ROTATION_COUNT - 1)) -1 1); do
101
- [[ -f "${log_file}.$i" ]] && mv "${log_file}.$i" "${log_file}.$((i + 1))" 2>/dev/null
102
- done
103
- mv "$log_file" "${log_file}.1" 2>/dev/null || true
104
- fi
105
- }
106
-
107
- # === Core Logging Function ===
108
- _log() {
109
- local level="$1"
110
- local message="$2"
111
- shift 2
112
- local extra_fields=("$@")
113
-
114
- _should_log "$level" || return 0
115
-
116
- local timestamp
117
- timestamp=$(get_timestamp_iso)
118
-
119
- local log_file="$LOG_DIR/${_LOG_HOOK_NAME:-hooks}.log"
120
- _rotate_log_if_needed "$log_file"
121
-
122
- if [[ "$LOG_FORMAT" == "json" ]]; then
123
- # Build JSON log entry
124
- local json="{\"ts\":\"$timestamp\",\"level\":\"$level\",\"hook\":\"${_LOG_HOOK_NAME:-unknown}\",\"rid\":\"${_LOG_REQUEST_ID:-none}\",\"msg\":\"$message\""
125
-
126
- # Add extra fields
127
- for field in "${extra_fields[@]}"; do
128
- local key="${field%%=*}"
129
- local value="${field#*=}"
130
- # Escape quotes in value
131
- value="${value//\"/\\\"}"
132
- json="$json,\"$key\":\"$value\""
133
- done
134
-
135
- json="$json}"
136
- echo "$json" >> "$log_file" 2>/dev/null || true
137
- else
138
- # Text format
139
- local prefix="[$timestamp] [$level] [${_LOG_HOOK_NAME:-unknown}] [${_LOG_REQUEST_ID:-none}]"
140
- echo "$prefix $message ${extra_fields[*]}" >> "$log_file" 2>/dev/null || true
141
- fi
142
-
143
- # Also output to stderr if DEBUG level and debug mode enabled
144
- if [[ "$level" == "DEBUG" ]] && [[ "${HOOK_DEBUG:-0}" == "1" ]]; then
145
- echo "[HOOK-$level] $_LOG_HOOK_NAME: $message" >&2
146
- fi
147
- }
148
-
149
- # === Public Logging Functions ===
150
- log_debug() {
151
- _log "DEBUG" "$1" "${@:2}"
152
- }
153
-
154
- log_info() {
155
- _log "INFO" "$1" "${@:2}"
156
- }
157
-
158
- log_warn() {
159
- _log "WARN" "$1" "${@:2}"
160
- }
161
-
162
- log_error() {
163
- _log "ERROR" "$1" "${@:2}"
164
- }
165
-
166
- # === Performance Timing ===
167
- log_timing_start() {
168
- local operation="$1"
169
- _LOG_TIMINGS[$operation]=$(get_timestamp_ms)
170
- }
171
-
172
- log_timing_end() {
173
- local operation="$1"
174
- local start_time="${_LOG_TIMINGS[$operation]:-0}"
175
-
176
- if [[ "$start_time" -gt 0 ]]; then
177
- local end_time
178
- end_time=$(get_timestamp_ms)
179
- local duration_ms=$((end_time - start_time))
180
-
181
- log_debug "Timing: $operation completed" "duration_ms=$duration_ms"
182
- unset "_LOG_TIMINGS[$operation]"
183
-
184
- echo "$duration_ms"
185
- else
186
- echo "0"
187
- fi
188
- }
189
-
190
- # === Request Summary ===
191
- log_request_summary() {
192
- local status="$1"
193
- local details="$2"
194
-
195
- local total_duration=0
196
- if [[ -n "$_LOG_START_TIME" ]]; then
197
- local end_time
198
- end_time=$(get_timestamp_ms)
199
- total_duration=$((end_time - _LOG_START_TIME))
200
- fi
201
-
202
- log_info "Request completed" "status=$status" "total_ms=$total_duration" "details=$details"
203
- }
204
-
205
- # === Aggregate Log Stats ===
206
- get_log_stats() {
207
- local hook_name="${1:-$_LOG_HOOK_NAME}"
208
- local log_file="$LOG_DIR/${hook_name}.log"
209
-
210
- [[ ! -f "$log_file" ]] && echo '{"total":0,"errors":0,"warns":0}' && return
211
-
212
- local total errors warns
213
- total=$(wc -l < "$log_file" 2>/dev/null | tr -d ' ')
214
- errors=$(grep -c '"level":"ERROR"' "$log_file" 2>/dev/null || echo 0)
215
- warns=$(grep -c '"level":"WARN"' "$log_file" 2>/dev/null || echo 0)
216
-
217
- echo "{\"total\":$total,\"errors\":$errors,\"warns\":$warns}"
218
- }
219
-
220
- # === Correlation ID Propagation ===
221
- # Use this to pass request ID to child processes
222
- export_request_context() {
223
- export REQUEST_ID="$_LOG_REQUEST_ID"
224
- export HOOK_NAME="$_LOG_HOOK_NAME"
225
- }
226
-
227
- # Import context from parent
228
- import_request_context() {
229
- _LOG_REQUEST_ID="${REQUEST_ID:-$(generate_request_id)}"
230
- _LOG_HOOK_NAME="${HOOK_NAME:-unknown}"
231
- }