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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -0
- package/hooks/agent-collaboration.sh +14 -1
- package/hooks/agent-handoff-prompt.sh +15 -4
- package/hooks/agent-ready-notification.sh +13 -2
- package/hooks/agent-usage-reminder.sh +12 -2
- package/hooks/anthropic-context-window-limit-recovery.sh +14 -2
- package/hooks/ast-grep.sh +14 -1
- package/hooks/atlas.sh +13 -4
- package/hooks/auto-update-checker.sh +20 -1
- package/hooks/background-compaction.sh +15 -2
- package/hooks/background-notification.sh +1 -1
- package/hooks/code-quality-checker.sh +14 -1
- package/hooks/compaction-context-injector.sh +218 -0
- package/hooks/context-compression.sh +14 -1
- package/hooks/context-smart-alert.sh +15 -3
- package/hooks/context-window-monitor.sh +15 -3
- package/hooks/delegate-task-retry.sh +4 -4
- package/hooks/directory-agents-injector.sh +14 -1
- package/hooks/directory-readme-injector.sh +16 -2
- package/hooks/edit-error-recovery.sh +17 -3
- package/hooks/empty-message-sanitizer.sh +150 -0
- package/hooks/empty-task-response-detector.sh +14 -3
- package/hooks/error-friendly-display.sh +17 -7
- package/hooks/error-recovery.sh +14 -1
- package/hooks/first-use-onboarding.sh +1 -4
- package/hooks/hook-performance-monitor.sh +1 -1
- package/hooks/hooks.json +30 -1
- package/hooks/interactive-bash-session.sh +12 -2
- package/hooks/lsp-tools.sh +14 -1
- package/hooks/non-interactive-env.sh +186 -0
- package/hooks/output-truncator.sh +14 -1
- package/hooks/preemptive-compaction.sh +14 -1
- package/hooks/rules-injector.sh +14 -1
- package/hooks/session-notification.sh +17 -3
- package/hooks/session-recovery.sh +12 -2
- package/hooks/task-checkpointing.sh +14 -1
- package/hooks/think-mode.sh +14 -1
- package/hooks/thinking-block-validator.sh +14 -3
- package/hooks/tmux-agent-visualizer.sh +17 -2
- 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.
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 '.
|
|
32
|
-
tool_output=$(echo "$input" | jq -r '.
|
|
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" |
|
|
36
|
-
tool_output=$(echo "$input" |
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
# 错误检测函数
|