claude-pangu 2.2.21 → 2.2.22

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 (41) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -0
  3. package/hooks/agent-collaboration.sh +14 -1
  4. package/hooks/agent-handoff-prompt.sh +15 -4
  5. package/hooks/agent-ready-notification.sh +13 -2
  6. package/hooks/agent-usage-reminder.sh +12 -2
  7. package/hooks/anthropic-context-window-limit-recovery.sh +14 -2
  8. package/hooks/ast-grep.sh +14 -1
  9. package/hooks/atlas.sh +13 -4
  10. package/hooks/auto-update-checker.sh +20 -1
  11. package/hooks/background-compaction.sh +15 -2
  12. package/hooks/background-notification.sh +1 -1
  13. package/hooks/code-quality-checker.sh +14 -1
  14. package/hooks/compaction-context-injector.sh +218 -0
  15. package/hooks/context-compression.sh +14 -1
  16. package/hooks/context-smart-alert.sh +15 -3
  17. package/hooks/context-window-monitor.sh +15 -3
  18. package/hooks/delegate-task-retry.sh +4 -4
  19. package/hooks/directory-agents-injector.sh +14 -1
  20. package/hooks/directory-readme-injector.sh +16 -2
  21. package/hooks/edit-error-recovery.sh +17 -3
  22. package/hooks/empty-message-sanitizer.sh +150 -0
  23. package/hooks/empty-task-response-detector.sh +14 -3
  24. package/hooks/error-friendly-display.sh +17 -7
  25. package/hooks/error-recovery.sh +14 -1
  26. package/hooks/first-use-onboarding.sh +1 -4
  27. package/hooks/hook-performance-monitor.sh +1 -1
  28. package/hooks/hooks.json +30 -1
  29. package/hooks/interactive-bash-session.sh +12 -2
  30. package/hooks/lsp-tools.sh +14 -1
  31. package/hooks/non-interactive-env.sh +186 -0
  32. package/hooks/output-truncator.sh +14 -1
  33. package/hooks/preemptive-compaction.sh +14 -1
  34. package/hooks/rules-injector.sh +14 -1
  35. package/hooks/session-notification.sh +17 -3
  36. package/hooks/session-recovery.sh +12 -2
  37. package/hooks/task-checkpointing.sh +14 -1
  38. package/hooks/think-mode.sh +14 -1
  39. package/hooks/thinking-block-validator.sh +14 -3
  40. package/hooks/tmux-agent-visualizer.sh +17 -2
  41. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://code.claude.com/plugin-schema.json",
3
3
  "name": "oh-my-claude",
4
- "version": "2.2.21",
4
+ "version": "2.2.22",
5
5
  "hooks": "../hooks/hooks.json",
6
6
  "description": "基于中国传统文化的 Claude Code 智能编排插件 - A Claude Code plugin inspired by Chinese traditional culture",
7
7
  "author": "ZDragon17",
package/README.md CHANGED
@@ -917,6 +917,7 @@ model: sonnet
917
917
  - [x] v2.2.2 - **第三阶段功能集成** 🔬(3 个新技能 + 5 个 Agent 变体)
918
918
  - [x] v2.2.17 - **Hooks 验证增强** ✅(安装验证显示 hooks 状态)
919
919
  - [x] v2.2.18 - **代码清理** 🧹(删除废弃 JS 文件,更新开发文档)
920
+ - [x] v2.2.22 - **Hook 系统根本性修复** 🔧(stdin JSON 读取、PostToolUse 崩溃修复、WSL 符号链接保护)
920
921
 
921
922
  ### v2.2.2 核心更新
922
923
 
@@ -161,5 +161,18 @@ main() {
161
161
  fi
162
162
  }
163
163
 
164
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
165
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
166
+ if [ -z "$_STDIN_INPUT" ]; then
167
+ exit 0
168
+ fi
169
+
170
+ # 解析 stdin JSON 的 prompt 字段
171
+ if command -v jq > /dev/null 2>&1; then
172
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | jq -r '.prompt // empty' 2>/dev/null) || _STDIN_PROMPT=""
173
+ else
174
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | grep -o '"prompt"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || _STDIN_PROMPT=""
175
+ fi
176
+
164
177
  # 执行主函数
165
- main "$@"
178
+ main "$_STDIN_PROMPT"
@@ -10,10 +10,21 @@
10
10
  # 错误捕获:任何错误都静默退出,不影响用户体验
11
11
  trap 'exit 0' ERR EXIT
12
12
 
13
- # 获取工具输出(带默认值保护)
14
- TOOL_OUTPUT="${CLAUDE_TOOL_OUTPUT:-}"
15
- TOOL_NAME="${CLAUDE_TOOL_NAME:-}"
16
- LAST_AGENT="${CLAUDE_LAST_AGENT:-}"
13
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
14
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
15
+ if [ -z "$_STDIN_INPUT" ]; then
16
+ exit 0
17
+ fi
18
+
19
+ # 解析 stdin JSON 字段
20
+ if command -v jq > /dev/null 2>&1; then
21
+ TOOL_NAME=$(echo "$_STDIN_INPUT" | jq -r '.tool_name // empty' 2>/dev/null) || TOOL_NAME=""
22
+ TOOL_OUTPUT=$(echo "$_STDIN_INPUT" | jq -r '(.tool_output // empty) | if type == "object" then tostring else . end' 2>/dev/null) || TOOL_OUTPUT=""
23
+ else
24
+ TOOL_NAME=$(echo "$_STDIN_INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || TOOL_NAME=""
25
+ TOOL_OUTPUT=$(echo "$_STDIN_INPUT" | grep -o '"tool_output"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_output"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || TOOL_OUTPUT=""
26
+ fi
27
+ LAST_AGENT=""
17
28
 
18
29
  # 安全检查:如果没有工具输出,静默退出
19
30
  if [ -z "$TOOL_OUTPUT" ]; then
@@ -6,8 +6,19 @@
6
6
 
7
7
  # 环境变量
8
8
  HOOK_NAME="agent-ready-notification"
9
- STOP_REASON="${CLAUDE_STOP_REASON:-}"
10
- SESSION_ID="${CLAUDE_SESSION_ID:-}"
9
+
10
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
11
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
12
+
13
+ # 解析 stdin JSON 字段(Stop 事件提供 session_id, transcript_path, cwd)
14
+ if command -v jq > /dev/null 2>&1; then
15
+ SESSION_ID=$(echo "$_STDIN_INPUT" | jq -r '.session_id // empty' 2>/dev/null) || SESSION_ID=""
16
+ else
17
+ SESSION_ID=$(echo "$_STDIN_INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"session_id"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || SESSION_ID=""
18
+ fi
19
+
20
+ # Stop 事件没有 stop_reason 字段
21
+ STOP_REASON=""
11
22
 
12
23
  # 配置 - 可通过环境变量自定义
13
24
  NOTIFICATION_ENABLED="${OH_MY_CLAUDE_NOTIFICATIONS:-true}"
@@ -20,8 +20,18 @@ STATE_DIR=".claude"
20
20
  REMINDER_STATE_FILE="$STATE_DIR/agent-reminder-state.json"
21
21
  COOLDOWN_SECONDS=600 # 10 分钟冷却期(增加,避免打扰)
22
22
 
23
- # 获取用户输入
24
- user_input="${CLAUDE_USER_PROMPT:-}"
23
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
24
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
25
+ if [ -z "$_STDIN_INPUT" ]; then
26
+ exit 0
27
+ fi
28
+
29
+ # 解析 stdin JSON 字段
30
+ if command -v jq > /dev/null 2>&1; then
31
+ user_input=$(echo "$_STDIN_INPUT" | jq -r '.prompt // empty' 2>/dev/null) || user_input=""
32
+ else
33
+ user_input=$(echo "$_STDIN_INPUT" | grep -o '"prompt"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || user_input=""
34
+ fi
25
35
 
26
36
  # 如果输入为空,直接退出
27
37
  if [ -z "$user_input" ]; then
@@ -5,8 +5,20 @@
5
5
 
6
6
  # 环境变量
7
7
  HOOK_NAME="anthropic-context-window-limit-recovery"
8
- TOOL_OUTPUT="${CLAUDE_TOOL_OUTPUT:-}"
9
- ERROR_MESSAGE="${CLAUDE_ERROR:-}"
8
+
9
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
10
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
11
+ if [ -z "$_STDIN_INPUT" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # 解析 stdin JSON 字段
16
+ if command -v jq > /dev/null 2>&1; then
17
+ TOOL_OUTPUT=$(echo "$_STDIN_INPUT" | jq -r '(.tool_output // empty) | if type == "object" then tostring else . end' 2>/dev/null) || TOOL_OUTPUT=""
18
+ else
19
+ TOOL_OUTPUT=$(echo "$_STDIN_INPUT" | grep -o '"tool_output"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_output"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || TOOL_OUTPUT=""
20
+ fi
21
+ ERROR_MESSAGE=""
10
22
 
11
23
  # 配置
12
24
  RECOVERY_STATE_FILE="${HOME}/.oh-my-claude/context-recovery.state"
package/hooks/ast-grep.sh CHANGED
@@ -516,5 +516,18 @@ main() {
516
516
  exit 0
517
517
  }
518
518
 
519
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
520
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
521
+ if [ -z "$_STDIN_INPUT" ]; then
522
+ exit 0
523
+ fi
524
+
525
+ # 解析 stdin JSON 的 prompt 字段
526
+ if command -v jq > /dev/null 2>&1; then
527
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | jq -r '.prompt // empty' 2>/dev/null) || _STDIN_PROMPT=""
528
+ else
529
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | grep -o '"prompt"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || _STDIN_PROMPT=""
530
+ fi
531
+
519
532
  # 执行主函数
520
- main "$@"
533
+ main "$_STDIN_PROMPT"
package/hooks/atlas.sh CHANGED
@@ -5,10 +5,19 @@
5
5
 
6
6
  # 环境变量
7
7
  HOOK_NAME="atlas"
8
- PROMPT="${CLAUDE_PROMPT:-}"
9
- TOOL_NAME="${CLAUDE_TOOL_NAME:-}"
10
- TOOL_INPUT="${CLAUDE_TOOL_INPUT:-}"
11
- TOOL_OUTPUT="${CLAUDE_TOOL_OUTPUT:-}"
8
+
9
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
10
+ INPUT=$(cat 2>/dev/null) || INPUT=""
11
+ if [ -z "$INPUT" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # 解析 stdin JSON 字段
16
+ if command -v jq > /dev/null 2>&1; then
17
+ PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null) || PROMPT=""
18
+ else
19
+ PROMPT=$(echo "$INPUT" | grep -o '"prompt"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || PROMPT=""
20
+ fi
12
21
 
13
22
  # 配置
14
23
  CONFIG_DIR="${HOME}/.oh-my-claude"
@@ -664,5 +664,24 @@ main() {
664
664
  exit 0
665
665
  }
666
666
 
667
+ # 检查是否通过命令行参数调用(如 SessionStart 的 --session-start)
668
+ if [ $# -gt 0 ]; then
669
+ main "$@"
670
+ exit $?
671
+ fi
672
+
673
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
674
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
675
+ if [ -z "$_STDIN_INPUT" ]; then
676
+ exit 0
677
+ fi
678
+
679
+ # 解析 stdin JSON 的 prompt 字段(UserPromptSubmit 事件)
680
+ if command -v jq > /dev/null 2>&1; then
681
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | jq -r '.prompt // empty' 2>/dev/null) || _STDIN_PROMPT=""
682
+ else
683
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | grep -o '"prompt"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || _STDIN_PROMPT=""
684
+ fi
685
+
667
686
  # 执行主函数
668
- main "$@"
687
+ main "$_STDIN_PROMPT"
@@ -5,8 +5,21 @@
5
5
 
6
6
  # 环境变量
7
7
  HOOK_NAME="background-compaction"
8
- TOOL_NAME="${CLAUDE_TOOL_NAME:-}"
9
- TOOL_OUTPUT="${CLAUDE_TOOL_OUTPUT:-}"
8
+
9
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
10
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
11
+ if [ -z "$_STDIN_INPUT" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # 解析 stdin JSON 字段
16
+ if command -v jq > /dev/null 2>&1; then
17
+ TOOL_NAME=$(echo "$_STDIN_INPUT" | jq -r '.tool_name // empty' 2>/dev/null) || TOOL_NAME=""
18
+ TOOL_OUTPUT=$(echo "$_STDIN_INPUT" | jq -r '(.tool_output // empty) | if type == "object" then tostring else . end' 2>/dev/null) || TOOL_OUTPUT=""
19
+ else
20
+ TOOL_NAME=$(echo "$_STDIN_INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || TOOL_NAME=""
21
+ TOOL_OUTPUT=$(echo "$_STDIN_INPUT" | grep -o '"tool_output"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_output"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || TOOL_OUTPUT=""
22
+ fi
10
23
 
11
24
  # 配置
12
25
  COMPACTION_THRESHOLD=5 # 触发压缩建议的后台任务数量阈值
@@ -25,7 +25,7 @@ failed_count=0
25
25
  completed_tasks=""
26
26
 
27
27
  # 遍历状态文件
28
- for state_file in "$BACKGROUND_STATE_DIR"/*.state 2>/dev/null; do
28
+ for state_file in "$BACKGROUND_STATE_DIR"/*.state; do
29
29
  [ -f "$state_file" ] || continue
30
30
 
31
31
  task_id=$(basename "$state_file" .state)
@@ -424,5 +424,18 @@ main() {
424
424
  exit 0
425
425
  }
426
426
 
427
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
428
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
429
+ if [ -z "$_STDIN_INPUT" ]; then
430
+ exit 0
431
+ fi
432
+
433
+ # 解析 stdin JSON 的 prompt 字段
434
+ if command -v jq > /dev/null 2>&1; then
435
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | jq -r '.prompt // empty' 2>/dev/null) || _STDIN_PROMPT=""
436
+ else
437
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | grep -o '"prompt"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || _STDIN_PROMPT=""
438
+ fi
439
+
427
440
  # 执行主函数
428
- main "$@"
441
+ main "$_STDIN_PROMPT"
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # Compaction Context Injector - 压缩上下文注入器
4
+ # ============================================================================
5
+ # 对标 oh-my-opencode 的 compaction-context-injector hook
6
+ # 在会话压缩(compaction)发生时,保留关键上下文信息
7
+ #
8
+ # Hook 类型: Stop
9
+ # 功能:
10
+ # 1. 检测会话压缩事件
11
+ # 2. 在压缩后注入关键上下文(活跃 TODO、当前任务状态)
12
+ # 3. 保留循环控制状态(Ralph Loop、愚公移山状态)
13
+ # 4. 保留重要的 Agent 协作上下文
14
+ # ============================================================================
15
+
16
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
17
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
18
+
19
+ # 解析 stdin JSON 字段(Stop 事件提供 session_id, transcript_path, cwd)
20
+ if command -v jq > /dev/null 2>&1; then
21
+ STOP_SESSION_ID=$(echo "$_STDIN_INPUT" | jq -r '.session_id // empty' 2>/dev/null) || STOP_SESSION_ID=""
22
+ else
23
+ STOP_SESSION_ID=$(echo "$_STDIN_INPUT" | grep -o '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"session_id"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || STOP_SESSION_ID=""
24
+ fi
25
+
26
+ # Stop 事件没有 stop_reason 字段,使用空值(压缩检测依赖其他标记文件)
27
+ STOP_REASON=""
28
+
29
+ # 状态文件路径
30
+ TODO_STATE_FILE="${HOME}/.oh-my-claude/todo-state.json"
31
+ RALPH_LOOP_FILE=".claude/ralph-loop.local.md"
32
+ YISHAN_STATE_FILE="${HOME}/.oh-my-claude/yishan-state.json"
33
+ COMPACTION_MARKER="${HOME}/.oh-my-claude/last-compaction.marker"
34
+ CONTEXT_CACHE="${HOME}/.oh-my-claude/compaction-context.cache"
35
+
36
+ # 确保目录存在
37
+ mkdir -p "${HOME}/.oh-my-claude"
38
+
39
+ # ============================================================================
40
+ # 检测函数
41
+ # ============================================================================
42
+
43
+ # 检测是否发生了会话压缩
44
+ detect_compaction() {
45
+ # 检查停止原因是否与压缩相关
46
+ if echo "$STOP_REASON" | grep -qiE '(compact|compaction|context.*limit|token.*limit|truncat)'; then
47
+ return 0
48
+ fi
49
+
50
+ # 检查上下文窗口限制标记
51
+ if [ -f "${HOME}/.oh-my-claude/context-limit-reached.flag" ]; then
52
+ rm -f "${HOME}/.oh-my-claude/context-limit-reached.flag"
53
+ return 0
54
+ fi
55
+
56
+ return 1
57
+ }
58
+
59
+ # ============================================================================
60
+ # 上下文收集函数
61
+ # ============================================================================
62
+
63
+ # 收集活跃 TODO 列表
64
+ collect_todo_context() {
65
+ local context=""
66
+
67
+ # 从 Claude Code 的 todos 目录读取
68
+ local todos_dir="${HOME}/.claude/todos"
69
+ if [ -d "$todos_dir" ]; then
70
+ # 查找最近的 todo 文件
71
+ local latest_todo
72
+ latest_todo=$(ls -t "$todos_dir"/*.json 2>/dev/null | head -1)
73
+ if [ -n "$latest_todo" ] && [ -f "$latest_todo" ]; then
74
+ # 提取未完成的 todo 项
75
+ local pending
76
+ pending=$(grep -o '"status":"pending"\|"status":"in_progress"' "$latest_todo" 2>/dev/null | wc -l | tr -d ' ')
77
+ local completed
78
+ completed=$(grep -o '"status":"completed"' "$latest_todo" 2>/dev/null | wc -l | tr -d ' ')
79
+
80
+ if [ "$pending" -gt 0 ]; then
81
+ context="**活跃 TODO**: ${completed} 已完成, ${pending} 待处理"
82
+ fi
83
+ fi
84
+ fi
85
+
86
+ echo "$context"
87
+ }
88
+
89
+ # 收集循环控制状态
90
+ collect_loop_context() {
91
+ local context=""
92
+
93
+ # 检查 Ralph Loop 状态
94
+ if [ -f "$RALPH_LOOP_FILE" ]; then
95
+ local iteration
96
+ iteration=$(grep '^iteration:' "$RALPH_LOOP_FILE" 2>/dev/null | sed 's/iteration:[[:space:]]*//' || echo "?")
97
+ local max_iter
98
+ max_iter=$(grep '^max_iterations:' "$RALPH_LOOP_FILE" 2>/dev/null | sed 's/max_iterations:[[:space:]]*//' || echo "100")
99
+ context="**Ralph Loop 活跃**: 第 ${iteration}/${max_iter} 次迭代"
100
+ fi
101
+
102
+ # 检查愚公移山状态
103
+ if [ -f "$YISHAN_STATE_FILE" ]; then
104
+ local active
105
+ active=$(grep -o '"active": *true' "$YISHAN_STATE_FILE" 2>/dev/null)
106
+ if [ -n "$active" ]; then
107
+ local mode
108
+ mode=$(grep -o '"mode": *"[^"]*"' "$YISHAN_STATE_FILE" 2>/dev/null | sed 's/"mode": *"//' | sed 's/"//')
109
+ context="${context:+$context\\n}**愚公移山模式活跃**: 模式=${mode:-标准}"
110
+ fi
111
+ fi
112
+
113
+ echo "$context"
114
+ }
115
+
116
+ # 收集工作目录上下文
117
+ collect_workspace_context() {
118
+ local context=""
119
+
120
+ # 当前工作目录
121
+ local cwd
122
+ cwd=$(pwd)
123
+ context="**工作目录**: ${cwd}"
124
+
125
+ # Git 分支信息
126
+ local branch
127
+ branch=$(git branch --show-current 2>/dev/null)
128
+ if [ -n "$branch" ]; then
129
+ context="${context}\\n**Git 分支**: ${branch}"
130
+
131
+ # 未提交的变更数
132
+ local changes
133
+ changes=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
134
+ if [ "$changes" -gt 0 ]; then
135
+ context="${context} (${changes} 个未提交变更)"
136
+ fi
137
+ fi
138
+
139
+ echo "$context"
140
+ }
141
+
142
+ # ============================================================================
143
+ # 缓存管理
144
+ # ============================================================================
145
+
146
+ # 保存上下文到缓存(供下次压缩使用)
147
+ save_context_cache() {
148
+ local context="$1"
149
+ echo "$context" > "$CONTEXT_CACHE"
150
+ }
151
+
152
+ # 读取缓存的上下文
153
+ read_context_cache() {
154
+ if [ -f "$CONTEXT_CACHE" ]; then
155
+ cat "$CONTEXT_CACHE"
156
+ fi
157
+ }
158
+
159
+ # ============================================================================
160
+ # 主逻辑
161
+ # ============================================================================
162
+
163
+ main() {
164
+ # 始终收集并缓存上下文(即使不是压缩事件,也为下次压缩准备)
165
+ local todo_ctx
166
+ todo_ctx=$(collect_todo_context)
167
+ local loop_ctx
168
+ loop_ctx=$(collect_loop_context)
169
+ local workspace_ctx
170
+ workspace_ctx=$(collect_workspace_context)
171
+
172
+ # 构建完整上下文
173
+ local full_context=""
174
+ if [ -n "$workspace_ctx" ]; then
175
+ full_context="$workspace_ctx"
176
+ fi
177
+ if [ -n "$todo_ctx" ]; then
178
+ full_context="${full_context:+$full_context\\n}$todo_ctx"
179
+ fi
180
+ if [ -n "$loop_ctx" ]; then
181
+ full_context="${full_context:+$full_context\\n}$loop_ctx"
182
+ fi
183
+
184
+ # 保存到缓存
185
+ if [ -n "$full_context" ]; then
186
+ save_context_cache "$full_context"
187
+ fi
188
+
189
+ # 如果检测到压缩事件,注入上下文
190
+ if detect_compaction; then
191
+ # 记录压缩时间
192
+ date -Iseconds > "$COMPACTION_MARKER" 2>/dev/null || date +%Y-%m-%dT%H:%M:%S > "$COMPACTION_MARKER"
193
+
194
+ # 构建注入消息
195
+ local inject_msg=""
196
+ inject_msg="\\n\\n[COMPACTION CONTEXT INJECTOR]\\n\\n"
197
+ inject_msg="${inject_msg}**会话已压缩** - 以下是压缩前的关键上下文:\\n\\n"
198
+
199
+ if [ -n "$full_context" ]; then
200
+ inject_msg="${inject_msg}${full_context}\\n\\n"
201
+ fi
202
+
203
+ # 添加恢复提示
204
+ inject_msg="${inject_msg}**重要提示**:\\n"
205
+ inject_msg="${inject_msg}- 如有活跃的 TODO 列表,请使用 TodoRead 工具查看当前状态\\n"
206
+ inject_msg="${inject_msg}- 如有活跃的循环(Ralph Loop/愚公移山),循环将自动继续\\n"
207
+ inject_msg="${inject_msg}- 使用 Read 工具重新获取需要的文件内容\\n"
208
+
209
+ printf '{"decision":"block","systemMessage":"%s"}\n' "$inject_msg"
210
+ exit 0
211
+ fi
212
+
213
+ # 非压缩事件,正常退出
214
+ exit 0
215
+ }
216
+
217
+ # 执行
218
+ main
@@ -146,5 +146,18 @@ main() {
146
146
  exit 0
147
147
  }
148
148
 
149
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
150
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
151
+ if [ -z "$_STDIN_INPUT" ]; then
152
+ exit 0
153
+ fi
154
+
155
+ # 解析 stdin JSON 的 prompt 字段
156
+ if command -v jq > /dev/null 2>&1; then
157
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | jq -r '.prompt // empty' 2>/dev/null) || _STDIN_PROMPT=""
158
+ else
159
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | grep -o '"prompt"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || _STDIN_PROMPT=""
160
+ fi
161
+
149
162
  # 执行主函数
150
- main "$@"
163
+ main "$_STDIN_PROMPT"
@@ -77,12 +77,24 @@ generate_progress_bar() {
77
77
  printf '%s' "$bar"
78
78
  }
79
79
 
80
- # 获取用户输入长度
81
- user_input="${CLAUDE_USER_PROMPT:-}"
80
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
81
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
82
+ if [ -z "$_STDIN_INPUT" ]; then
83
+ exit 0
84
+ fi
85
+
86
+ # 解析 stdin JSON 字段
87
+ if command -v jq > /dev/null 2>&1; then
88
+ user_input=$(echo "$_STDIN_INPUT" | jq -c '.tool_input // empty' 2>/dev/null) || user_input=""
89
+ tool_result=$(echo "$_STDIN_INPUT" | jq -r '(.tool_output // empty) | if type == "object" then tostring else . end' 2>/dev/null) || tool_result=""
90
+ else
91
+ user_input=$(echo "$_STDIN_INPUT" | grep -o '"tool_input"[[:space:]]*:[[:space:]]*{[^}]*}' | head -1 2>/dev/null) || user_input=""
92
+ tool_result=$(echo "$_STDIN_INPUT" | grep -o '"tool_output"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_output"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || tool_result=""
93
+ fi
94
+
82
95
  input_length=${#user_input}
83
96
 
84
97
  # 获取工具调用结果长度
85
- tool_result="${CLAUDE_TOOL_RESULT:-}"
86
98
  tool_result_length=${#tool_result}
87
99
 
88
100
  # 获取或初始化累积上下文估算
@@ -16,12 +16,24 @@ CONTEXT_CRITICAL_THRESHOLD=85 # 严重警告阈值(百分比)
16
16
  STATE_DIR=".claude"
17
17
  CONTEXT_STATE_FILE="$STATE_DIR/context-state.json"
18
18
 
19
- # 获取用户输入长度(粗略估算)
20
- user_input="${CLAUDE_USER_PROMPT:-}"
19
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
20
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
21
+ if [ -z "$_STDIN_INPUT" ]; then
22
+ exit 0
23
+ fi
24
+
25
+ # 解析 stdin JSON 字段
26
+ if command -v jq > /dev/null 2>&1; then
27
+ user_input=$(echo "$_STDIN_INPUT" | jq -c '.tool_input // empty' 2>/dev/null) || user_input=""
28
+ tool_result=$(echo "$_STDIN_INPUT" | jq -r '(.tool_output // empty) | if type == "object" then tostring else . end' 2>/dev/null) || tool_result=""
29
+ else
30
+ user_input=$(echo "$_STDIN_INPUT" | grep -o '"tool_input"[[:space:]]*:[[:space:]]*{[^}]*}' | head -1 2>/dev/null) || user_input=""
31
+ tool_result=$(echo "$_STDIN_INPUT" | grep -o '"tool_output"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_output"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || tool_result=""
32
+ fi
33
+
21
34
  input_length=${#user_input}
22
35
 
23
36
  # 获取工具调用结果长度
24
- tool_result="${CLAUDE_TOOL_RESULT:-}"
25
37
  tool_result_length=${#tool_result}
26
38
 
27
39
  # 获取或初始化累积上下文估算
@@ -28,12 +28,12 @@ fi
28
28
 
29
29
  # 提取工具输出
30
30
  if [ "$has_jq" -eq 1 ]; then
31
- tool_name=$(echo "$input" | jq -r '.tool // empty' 2>/dev/null) || tool_name=""
32
- tool_output=$(echo "$input" | jq -r '.output // empty' 2>/dev/null) || tool_output=""
31
+ tool_name=$(echo "$input" | jq -r '.tool_name // empty' 2>/dev/null) || tool_name=""
32
+ tool_output=$(echo "$input" | jq -r '(.tool_output // empty) | if type == "object" then tostring else . end' 2>/dev/null) || tool_output=""
33
33
  else
34
34
  # 简单的字符串提取
35
- tool_name=$(echo "$input" | sed -n 's/.*"tool"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' 2>/dev/null) || tool_name=""
36
- tool_output=$(echo "$input" | sed -n 's/.*"output"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' 2>/dev/null) || tool_output=""
35
+ tool_name=$(echo "$input" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || tool_name=""
36
+ tool_output=$(echo "$input" | grep -o '"tool_output"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_output"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || tool_output=""
37
37
  fi
38
38
 
39
39
  # 只处理 Task 相关工具
@@ -463,5 +463,18 @@ main() {
463
463
  exit 0
464
464
  }
465
465
 
466
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
467
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
468
+ if [ -z "$_STDIN_INPUT" ]; then
469
+ exit 0
470
+ fi
471
+
472
+ # 解析 stdin JSON 的 prompt 字段
473
+ if command -v jq > /dev/null 2>&1; then
474
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | jq -r '.prompt // empty' 2>/dev/null) || _STDIN_PROMPT=""
475
+ else
476
+ _STDIN_PROMPT=$(echo "$_STDIN_INPUT" | grep -o '"prompt"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || _STDIN_PROMPT=""
477
+ fi
478
+
466
479
  # 执行主函数
467
- main "$@"
480
+ main "$_STDIN_PROMPT"
@@ -5,8 +5,22 @@
5
5
 
6
6
  # 环境变量
7
7
  HOOK_NAME="directory-readme-injector"
8
- PROMPT="${CLAUDE_PROMPT:-}"
9
- WORKING_DIR="${CLAUDE_WORKING_DIR:-$(pwd)}"
8
+
9
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
10
+ _STDIN_INPUT=$(cat 2>/dev/null) || _STDIN_INPUT=""
11
+ if [ -z "$_STDIN_INPUT" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # 解析 stdin JSON 字段
16
+ if command -v jq > /dev/null 2>&1; then
17
+ PROMPT=$(echo "$_STDIN_INPUT" | jq -r '.prompt // empty' 2>/dev/null) || PROMPT=""
18
+ WORKING_DIR=$(echo "$_STDIN_INPUT" | jq -r '.cwd // empty' 2>/dev/null) || WORKING_DIR=""
19
+ else
20
+ PROMPT=$(echo "$_STDIN_INPUT" | grep -o '"prompt"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || PROMPT=""
21
+ WORKING_DIR=$(echo "$_STDIN_INPUT" | grep -o '"cwd"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"cwd"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || WORKING_DIR=""
22
+ fi
23
+ WORKING_DIR="${WORKING_DIR:-$(pwd)}"
10
24
 
11
25
  # 配置
12
26
  MAX_README_SIZE=5000 # 最大 README 内容长度
@@ -5,9 +5,23 @@
5
5
 
6
6
  # 环境变量
7
7
  HOOK_NAME="edit-error-recovery"
8
- TOOL_NAME="${CLAUDE_TOOL_NAME:-}"
9
- TOOL_OUTPUT="${CLAUDE_TOOL_OUTPUT:-}"
10
- TOOL_INPUT="${CLAUDE_TOOL_INPUT:-}"
8
+
9
+ # 从 stdin 读取 JSON 数据(Claude Code Hook API 通过 stdin 传递事件数据)
10
+ INPUT=$(cat 2>/dev/null) || INPUT=""
11
+ if [ -z "$INPUT" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # 解析 stdin JSON 字段
16
+ if command -v jq > /dev/null 2>&1; then
17
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) || TOOL_NAME=""
18
+ TOOL_OUTPUT=$(echo "$INPUT" | jq -r '(.tool_output // empty) | if type == "object" then tostring else . end' 2>/dev/null) || TOOL_OUTPUT=""
19
+ TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // empty' 2>/dev/null) || TOOL_INPUT=""
20
+ else
21
+ TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || TOOL_NAME=""
22
+ TOOL_OUTPUT=$(echo "$INPUT" | grep -o '"tool_output"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_output"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' 2>/dev/null) || TOOL_OUTPUT=""
23
+ TOOL_INPUT=$(echo "$INPUT" | grep -o '"tool_input"[[:space:]]*:[[:space:]]*{[^}]*}' | head -1 2>/dev/null) || TOOL_INPUT=""
24
+ fi
11
25
 
12
26
  # ============================================================================
13
27
  # 错误检测函数