add-coder 0.2.8 → 0.3.0
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/README.md +66 -27
- package/dist/index.js +158 -11
- package/package.json +9 -12
- package/templates/.add-coder-src-hash.json +256 -0
- package/templates/adapters/claude/hooks/doc-format-guard.sh +17 -0
- package/templates/adapters/claude/hooks/lib/notify.sh +34 -0
- package/templates/adapters/claude/hooks/pre-tool-use.sh +26 -7
- package/templates/adapters/claude/hooks/prompt-submit.sh +16 -0
- package/templates/adapters/codex/hooks/doc-format-guard.sh +17 -0
- package/templates/adapters/codex/hooks/lib/notify.sh +34 -0
- package/templates/adapters/codex/hooks/pre-tool-use.sh +48 -18
- package/templates/adapters/codex/hooks/prompt-submit.sh +16 -0
- package/templates/adapters/qoder/hooks/doc-format-guard.sh +17 -0
- package/templates/adapters/qoder/hooks/lib/notify.sh +34 -0
- package/templates/adapters/qoder/hooks/pre-tool-use.sh +28 -9
- package/templates/adapters/qoder/hooks/prompt-submit.sh +18 -1
- package/templates/adapters/trae/hooks/doc-format-guard.sh +17 -0
- package/templates/adapters/trae/hooks/lib/notify.sh +34 -0
- package/templates/adapters/trae/hooks/pre-tool-use.sh +48 -18
- package/templates/adapters/trae/hooks/prompt-submit.sh +16 -0
- package/templates/adapters/vscode/hooks/doc-format-guard.sh +16 -0
- package/templates/adapters/vscode/hooks/lib/notify.sh +34 -0
- package/templates/adapters/vscode/hooks/pre-tool-use.sh +26 -7
- package/templates/adapters/vscode/hooks/prompt-submit.sh +16 -0
- package/templates/core/hooks/doc-format-guard.sh +17 -0
- package/templates/core/hooks/lib/notify.sh +34 -0
- package/templates/core/hooks/pre-tool-use.sh +48 -18
- package/templates/core/hooks/prompt-submit.sh +16 -0
- package/templates/core/reviews/.gitkeep +0 -0
- package/templates/core/scripts/mcp-server/elicitation/confirm.ts +30 -0
- package/templates/core/scripts/mcp-server/elicitation/index.ts +1 -0
- package/templates/core/scripts/mcp-server/index.ts +15 -0
- package/templates/core/scripts/mcp-server/notifications/hitl.ts +49 -0
- package/templates/core/scripts/mcp-server/notifications/hook.ts +268 -0
- package/templates/core/scripts/mcp-server/notifications/index.ts +8 -0
- package/templates/core/scripts/mcp-server/resources/add-coder-version.ts +25 -0
- package/templates/core/scripts/mcp-server/resources/add-state.ts +44 -0
- package/templates/core/scripts/mcp-server/resources/hook-events-report.ts +104 -0
- package/templates/core/scripts/mcp-server/resources/index.ts +12 -0
- package/templates/core/scripts/mcp-server/resources/round-task.ts +22 -0
- package/templates/core/scripts/mcp-server/sampling/index.ts +1 -0
- package/templates/core/scripts/mcp-server/sampling/review.ts +47 -0
- package/templates/core/scripts/mcp-server/shared/env.ts +26 -0
- package/templates/core/scripts/mcp-server/shared/fs.ts +40 -0
- package/templates/core/scripts/mcp-server/shared/prisma.ts +35 -0
- package/templates/core/scripts/mcp-server/shared/response.ts +9 -0
- package/templates/core/scripts/mcp-server/tasks/index.ts +2 -0
- package/templates/core/scripts/mcp-server/tasks/runner.ts +20 -0
- package/templates/core/scripts/mcp-server/tasks/store.ts +20 -0
- package/templates/core/scripts/mcp-server/tools/audit.ts +73 -0
- package/templates/core/scripts/mcp-server/tools/context.ts +337 -0
- package/templates/core/scripts/mcp-server/tools/docs.ts +42 -0
- package/templates/core/scripts/mcp-server/tools/gateway.ts +135 -0
- package/templates/core/scripts/mcp-server/tools/hook-event-report.ts +92 -0
- package/templates/core/scripts/mcp-server/tools/index.ts +17 -0
- package/templates/core/scripts/mcp-server/tools/quality.ts +93 -0
- package/templates/core/scripts/mcp-server/types.ts +7 -0
- package/templates/core/scripts/mcp-server.ts +8 -3455
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# pre-tool-use.sh — PreToolUse §A§B(阻断模式,通用适配版)
|
|
3
3
|
# §A: Bash 裸写保护 — 拦截所有绕过 IDE 追踪的文件写操作
|
|
4
|
-
# §B:
|
|
4
|
+
# §B: Write/Edit 写入前置守卫 — Plan/Spec/Review 需要活跃 ADD Plan + 敏感文件保护
|
|
5
5
|
# 治理卡位 #4: 危险命令拦截 / 模板路径兜底 / 写入前置守卫 / 敏感文件保护
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
@@ -13,6 +13,17 @@ PARENT="$(dirname "$HOOK_DIR")"
|
|
|
13
13
|
MAGIC_DIR="$(basename "$PARENT")"
|
|
14
14
|
PROJECT_DIR="$PWD"
|
|
15
15
|
|
|
16
|
+
# ── Hook 通知: 拦截事件写入 jsonl(旁路,失败不阻断 exit 2)──
|
|
17
|
+
source "${HOOK_DIR}/lib/notify.sh" 2>/dev/null || true
|
|
18
|
+
ACTIVE_PLAN=$(detect_active_add 2>/dev/null || true)
|
|
19
|
+
if [ -n "$ACTIVE_PLAN" ]; then
|
|
20
|
+
PLAN_KEYWORD="${ACTIVE_PLAN%%::*}"
|
|
21
|
+
PLAN_STATUS="active"
|
|
22
|
+
else
|
|
23
|
+
PLAN_KEYWORD="no-active-plan"
|
|
24
|
+
PLAN_STATUS="none"
|
|
25
|
+
fi
|
|
26
|
+
|
|
16
27
|
# ── §A 辅助函数: 阻断日志 ──
|
|
17
28
|
_log_block() {
|
|
18
29
|
local rule="$1" cmd="$2"
|
|
@@ -32,7 +43,7 @@ command=$(echo "$input" | jq -r '.tool_input.command // empty')
|
|
|
32
43
|
if [ -n "$command" ]; then
|
|
33
44
|
|
|
34
45
|
# 检测 1: 脚本解释器 — 可写任意文件,无法解析脚本内容做细粒度拦截
|
|
35
|
-
if echo "$command" | grep -qE '
|
|
46
|
+
if echo "$command" | grep -qE '(^|;|\|\||&&|\|)\s*(python3?|node|ruby|perl|php)(\s|$)'; then
|
|
36
47
|
_reason="禁止通过脚本解释器直接修改文件。请使用 Write 或 SearchReplace 工具操作文件。"
|
|
37
48
|
cat >&2 <<'EOF'
|
|
38
49
|
⛔ [ADD PreToolUse §A] 阻断: 禁止通过脚本解释器直接修改文件。
|
|
@@ -47,6 +58,7 @@ if [ -n "$command" ]; then
|
|
|
47
58
|
EOF
|
|
48
59
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
49
60
|
_log_block "脚本解释器" "$command"
|
|
61
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过脚本解释器直接修改文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
50
62
|
exit 2
|
|
51
63
|
fi
|
|
52
64
|
|
|
@@ -61,6 +73,7 @@ EOF
|
|
|
61
73
|
EOF
|
|
62
74
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
63
75
|
_log_block "sed -i" "$command"
|
|
76
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过 sed -i 原地编辑" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
64
77
|
exit 2
|
|
65
78
|
fi
|
|
66
79
|
|
|
@@ -75,6 +88,7 @@ EOF
|
|
|
75
88
|
EOF
|
|
76
89
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
77
90
|
_log_block "重定向" "$command"
|
|
91
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过重定向写入文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
78
92
|
exit 2
|
|
79
93
|
fi
|
|
80
94
|
|
|
@@ -88,11 +102,12 @@ EOF
|
|
|
88
102
|
EOF
|
|
89
103
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
90
104
|
_log_block "tee/dd" "$command"
|
|
105
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过 tee/dd 写入文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
91
106
|
exit 2
|
|
92
107
|
fi
|
|
93
108
|
|
|
94
109
|
# 检测 5: cp / mv / touch — 可创建或覆盖文件
|
|
95
|
-
if echo "$command" | grep -qE '
|
|
110
|
+
if echo "$command" | grep -qE '(^|;|\|\||&&|\|)\s*(cp|mv|touch)\b'; then
|
|
96
111
|
_reason="禁止通过 cp/mv/touch 操作文件。请使用 Write 或 SearchReplace 工具。"
|
|
97
112
|
cat >&2 <<'EOF'
|
|
98
113
|
⛔ [ADD PreToolUse §A] 阻断: 禁止通过 cp/mv/touch 操作文件。
|
|
@@ -101,6 +116,7 @@ EOF
|
|
|
101
116
|
EOF
|
|
102
117
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
103
118
|
_log_block "cp/mv/touch" "$command"
|
|
119
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过 cp/mv/touch 操作文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
104
120
|
exit 2
|
|
105
121
|
fi
|
|
106
122
|
|
|
@@ -109,23 +125,37 @@ EOF
|
|
|
109
125
|
exit 0
|
|
110
126
|
fi
|
|
111
127
|
|
|
112
|
-
# ═══════════════ §B:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
# ═══════════════ §B: Write/Edit 文件写入前置守卫 ═══════════════
|
|
129
|
+
# 非 Bash 工具(Write/Edit)的文件写入需要经过:
|
|
130
|
+
# 1. Plan/Spec/Review 文档写入 → 需要活跃 ADD Plan
|
|
131
|
+
# 2. 敏感文件保护 → 阻断写入
|
|
132
|
+
# 3. Dev Action 标记 → 用于 Stop 检查
|
|
133
|
+
# 注意: 不再对 src/**/*.ts 做 Plan 白名单放行,避免绕过 skill/rules 规定的 HITL。
|
|
134
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
135
|
+
if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ]; then
|
|
136
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
|
137
|
+
[ -z "$file_path" ] && exit 0
|
|
138
|
+
|
|
139
|
+
if echo "$file_path" | grep -qE '\.(qoder|claude|add)/(plans|specs|reviews)/'; then
|
|
140
|
+
if type detect_active_add >/dev/null 2>&1; then
|
|
141
|
+
state=$(detect_active_add 2>/dev/null || true)
|
|
142
|
+
if [ -z "$state" ]; then
|
|
143
|
+
echo "⛔ 正在写入 Plan/Spec/Review 文档但无活跃 ADD Plan——请先执行 add-paradigm" >&2
|
|
144
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Plan/Spec/Review 写入需要活跃 ADD Plan,请先执行 add-paradigm\"}}"
|
|
145
|
+
write_hook_event "pre-tool-use" "deny" "$tool_name $file_path" "Plan/Spec/Review 写入需活跃 ADD Plan" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
146
|
+
exit 2
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
121
150
|
|
|
122
|
-
|
|
151
|
+
if echo "$file_path" | grep -qE '\.env$|\.env\.production$|\.env\.local$|credentials|secrets'; then
|
|
152
|
+
echo "⛔ 敏感文件受保护,禁止写入: $file_path" >&2
|
|
153
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"敏感文件受保护\"}}"
|
|
154
|
+
exit 2
|
|
155
|
+
fi
|
|
123
156
|
|
|
124
|
-
|
|
125
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"permissionDecisionReason\":\"相关 ADD Plan 已存在\"}}"
|
|
157
|
+
mark_dev_action 2>/dev/null || true
|
|
126
158
|
exit 0
|
|
127
159
|
fi
|
|
128
160
|
|
|
129
|
-
|
|
130
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"无相关 ADD Plan。请用 AskUserQuestion 询问用户选择 plan-template 或 simple-plan-template 创建 Plan。\"}}"
|
|
131
|
-
exit 2
|
|
161
|
+
exit 0
|
|
@@ -10,6 +10,7 @@ prompt=$(echo "$input" | jq -r '.prompt // empty')
|
|
|
10
10
|
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
11
11
|
source "$HOOK_DIR/lib/vocabulary.sh" 2>/dev/null || true
|
|
12
12
|
source "$HOOK_DIR/lib/state-detect.sh" 2>/dev/null || true
|
|
13
|
+
source "$HOOK_DIR/lib/notify.sh" 2>/dev/null || true
|
|
13
14
|
|
|
14
15
|
# ─── Layer 1: 精准 P0 触发词 ───
|
|
15
16
|
matched=$(match_trigger "$prompt" 2>/dev/null || true)
|
|
@@ -63,6 +64,7 @@ if [ -z "$state" ]; then
|
|
|
63
64
|
Step 8: 收敛判断
|
|
64
65
|
如果 add-paradigm SKILL 尚未激活,请先调用它。
|
|
65
66
|
EOF
|
|
67
|
+
write_hook_event "prompt-submit" "deny" "$prompt" "无活跃 ADD Plan 下检测到开发任务" "no-active-plan" "none" 2>/dev/null || true
|
|
66
68
|
exit 2
|
|
67
69
|
fi
|
|
68
70
|
|
|
@@ -72,4 +74,18 @@ step=$(echo "$state" | awk -F'::' '{print $2}')
|
|
|
72
74
|
rounds=$(echo "$state" | awk -F'::' '{print $3}')
|
|
73
75
|
handoff=$(echo "$state" | awk -F'::' '{print $4}')
|
|
74
76
|
echo "[ADD 状态] Plan: ${plan}, 轮次: ${rounds}, Step: ${step}, handoff: ${handoff}"
|
|
77
|
+
|
|
78
|
+
# ─── Hook 治理日报(注入 AI 上下文)───
|
|
79
|
+
HOOK_JSONL="${MAGIC_DIR:-.qoder}/reports/hook-events.jsonl"
|
|
80
|
+
if [ -f "$HOOK_JSONL" ]; then
|
|
81
|
+
TODAY="$(date +%Y-%m-%d)"
|
|
82
|
+
TOTAL=$(grep -c "\"ts\":\"${TODAY}" "$HOOK_JSONL" 2>/dev/null || echo 0)
|
|
83
|
+
NO_PLAN=$(grep "\"ts\":\"${TODAY}" "$HOOK_JSONL" 2>/dev/null | grep -c '"planKeyword":"no-active-plan"' || echo 0)
|
|
84
|
+
if [ "$TOTAL" -gt 0 ] 2>/dev/null; then
|
|
85
|
+
echo "[Hook 治理] 今日拦截: ${TOTAL} 次 | 无 Plan 违规: ${NO_PLAN} 次"
|
|
86
|
+
if [ "$NO_PLAN" -ge 10 ] 2>/dev/null; then
|
|
87
|
+
echo "[Hook ⚠️] 无 Plan 违规已达 ${NO_PLAN} 次(≥10),建议创建 Plan 或检查 hooks 误报"
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
75
91
|
exit 0
|
|
@@ -8,6 +8,18 @@ input=$(cat)
|
|
|
8
8
|
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
9
|
PARENT_DIR="$(dirname "$HOOK_DIR")"
|
|
10
10
|
MAGIC_DIR="$(basename "$PARENT_DIR")"
|
|
11
|
+
PROJECT_DIR="${QODER_PROJECT_DIR:-${QODERCN_PROJECT_DIR:-$PWD}}"
|
|
12
|
+
|
|
13
|
+
# ── Hook 通知: 拦截事件写入 jsonl(旁路,失败不阻断 exit 2)──
|
|
14
|
+
source "${HOOK_DIR}/lib/notify.sh" 2>/dev/null || true
|
|
15
|
+
ACTIVE_PLAN=$(detect_active_add 2>/dev/null || true)
|
|
16
|
+
if [ -n "$ACTIVE_PLAN" ]; then
|
|
17
|
+
PLAN_KEYWORD="${ACTIVE_PLAN%%::*}"
|
|
18
|
+
PLAN_STATUS="active"
|
|
19
|
+
else
|
|
20
|
+
PLAN_KEYWORD="no-active-plan"
|
|
21
|
+
PLAN_STATUS="none"
|
|
22
|
+
fi
|
|
11
23
|
|
|
12
24
|
# DEBUG: dump stdin for investigation
|
|
13
25
|
mkdir -p "$MAGIC_DIR/debug-dump"
|
|
@@ -44,6 +56,7 @@ CONTENT=$(echo "$input" | jq -r '
|
|
|
44
56
|
if [ -z "$CONTENT" ]; then
|
|
45
57
|
echo "⛔ 拒绝:Write 工具未传 file_content,无法校验手写文档格式" >&2
|
|
46
58
|
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"Write 工具未传 file_content,无法校验手写文档。请用 SearchReplace 改写已有文件,或用 Write 工具重试。"}}'
|
|
59
|
+
write_hook_event "doc-format-guard" "deny" "Write" "Write 工具未传 file_content" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
47
60
|
exit 2
|
|
48
61
|
fi
|
|
49
62
|
|
|
@@ -93,6 +106,7 @@ if [ -z "$TEMPLATE_NAME" ]; then
|
|
|
93
106
|
else
|
|
94
107
|
echo "⛔ handoff 文件内容无法识别模板类型(缺 '## 全局元信息' 或 '## 1. 交接前状态'),拒绝写入" >&2
|
|
95
108
|
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"handoff 内容不符合 single/multi 模板规范"}}'
|
|
109
|
+
write_hook_event "doc-format-guard" "deny" "$file_path" "handoff 模板类型无法识别" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
96
110
|
exit 2
|
|
97
111
|
fi
|
|
98
112
|
;;
|
|
@@ -113,6 +127,7 @@ if [ -z "$TEMPLATE_NAME" ]; then
|
|
|
113
127
|
fi
|
|
114
128
|
echo "⛔ 拒绝:无法识别文档类型 (file_path: $file_path),缺少模板匹配规则" >&2
|
|
115
129
|
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"无法识别 ADD 文档类型,请联系管理员更新 doc-format-guard.sh"}}'
|
|
130
|
+
write_hook_event "doc-format-guard" "deny" "$file_path" "无法识别文档类型" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
116
131
|
exit 2
|
|
117
132
|
;;
|
|
118
133
|
esac
|
|
@@ -124,6 +139,7 @@ SCHEMA_FILE="$TEMPLATES_DIR/${TEMPLATE_NAME%.md}.schema.json"
|
|
|
124
139
|
if [ ! -f "$SCHEMA_FILE" ]; then
|
|
125
140
|
echo "⛔ 阻断:模板 ${TEMPLATE_NAME} 缺少对应的 .schema.json 校验规则" >&2
|
|
126
141
|
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"缺少 .schema.json 校验规则文件,禁止无规则放行"}}'
|
|
142
|
+
write_hook_event "doc-format-guard" "deny" "$file_path" "缺少 .schema.json 校验规则" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
127
143
|
exit 2
|
|
128
144
|
fi
|
|
129
145
|
|
|
@@ -230,6 +246,7 @@ if [ -n "$ISSUES" ]; then
|
|
|
230
246
|
echo "⛔ $TEMPLATE_NAME 校验不通过:
|
|
231
247
|
$ISSUES" >&2
|
|
232
248
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"文档格式校验不通过,请对照模板修正\"}}"
|
|
249
|
+
write_hook_event "doc-format-guard" "deny" "$file_path" "文档格式校验不通过" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
233
250
|
exit 2
|
|
234
251
|
fi
|
|
235
252
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# notify.sh — Hook 拦截事件写入 jsonl(零外部依赖,bash 原生)
|
|
3
|
+
# 用法: source 后在 exit 2 前调用 write_hook_event
|
|
4
|
+
#
|
|
5
|
+
# 写入格式(7 字段 jsonl):
|
|
6
|
+
# {"ts":"ISO8601","hook":"...","decision":"deny","cmd":"...","reason":"...","planKeyword":"...","planStatus":"..."}
|
|
7
|
+
#
|
|
8
|
+
# 磁盘管理:
|
|
9
|
+
# - 单文件 ≤256KB,超限轮转为 .old(覆盖,不累积多份)
|
|
10
|
+
# - 总量 ≤512KB(当前 + 一个 .old)
|
|
11
|
+
# - MCP Server 宕机不丢事件,重启后从文件恢复消费
|
|
12
|
+
|
|
13
|
+
write_hook_event() {
|
|
14
|
+
local hook="$1" decision="$2" cmd="$3" reason="$4" plan="${5:-unknown}" status="${6:-none}"
|
|
15
|
+
local dir="${MAGIC_DIR:-.qoder}/reports"
|
|
16
|
+
mkdir -p "$dir" 2>/dev/null || true
|
|
17
|
+
local file="$dir/hook-events.jsonl"
|
|
18
|
+
|
|
19
|
+
# 超过 256KB 自动轮转(262144 bytes)
|
|
20
|
+
if [ -f "$file" ]; then
|
|
21
|
+
local sz
|
|
22
|
+
sz=$(stat -c%s "$file" 2>/dev/null || echo 0)
|
|
23
|
+
[ "$sz" -gt 262144 ] && mv "$file" "${file}.old" 2>/dev/null || true
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
printf '{"ts":"%s","hook":"%s","decision":"%s","cmd":"%s","reason":"%s","planKeyword":"%s","planStatus":"%s"}\n' \
|
|
27
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
28
|
+
"$hook" \
|
|
29
|
+
"$decision" \
|
|
30
|
+
"$cmd" \
|
|
31
|
+
"$reason" \
|
|
32
|
+
"$plan" \
|
|
33
|
+
"$status" >> "$file"
|
|
34
|
+
}
|
|
@@ -8,6 +8,17 @@ HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
8
8
|
export CURRENT_MAGIC=$(basename "$(dirname "$HOOK_DIR")")
|
|
9
9
|
export PROJECT_DIR="${QODER_PROJECT_DIR:-${QODERCN_PROJECT_DIR:-$PWD}}"
|
|
10
10
|
source "$HOOK_DIR/lib/common.sh" 2>/dev/null || true
|
|
11
|
+
source "$HOOK_DIR/lib/notify.sh" 2>/dev/null || true
|
|
12
|
+
|
|
13
|
+
# ── Hook 通知: 提前计算 Plan 关联信息 ──
|
|
14
|
+
ACTIVE_PLAN=$(detect_active_add 2>/dev/null || true)
|
|
15
|
+
if [ -n "$ACTIVE_PLAN" ]; then
|
|
16
|
+
PLAN_KEYWORD="${ACTIVE_PLAN%%::*}"
|
|
17
|
+
PLAN_STATUS="active"
|
|
18
|
+
else
|
|
19
|
+
PLAN_KEYWORD="no-active-plan"
|
|
20
|
+
PLAN_STATUS="none"
|
|
21
|
+
fi
|
|
11
22
|
|
|
12
23
|
tool_name=$(json_get "$input" "tool_name")
|
|
13
24
|
[ -z "$tool_name" ] && tool_name=$(echo "$input" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' 2>/dev/null | sed 's/.*: *"\([^"]*\)".*/\1/' || echo "")
|
|
@@ -19,24 +30,28 @@ if [ "$tool_name" = "Bash" ]; then
|
|
|
19
30
|
if echo "$cmd" | grep -qiE 'rm[[:space:]]+-rf[[:space:]]+/|DROP[[:space:]]+TABLE|git[[:space:]]+push[[:space:]]+--force|mkfs\.|dd[[:space:]]+if='; then
|
|
20
31
|
echo "⛔ 危险命令已被阻止: $cmd" >&2
|
|
21
32
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"危险命令已被阻止\"}}"
|
|
33
|
+
write_hook_event "pre-tool-use" "deny" "$cmd" "危险命令已被阻止" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
22
34
|
exit 2
|
|
23
35
|
fi
|
|
24
36
|
# 终端写文件拦截(含 > / >> / << heredoc / mv / touch / python -c > file)
|
|
25
37
|
if echo "$cmd" | grep -qE '(cat|echo|tee|sed[[:space:]]+-i|awk|printf|cp|mv|dd|touch)[[:space:]]*.*([>]{1,2}|[|][[:space:]]*tee|<<)'; then
|
|
26
38
|
echo "⛔ 禁止通过终端命令直接写文件: $cmd。请使用 Write/Edit/SearchReplace 工具。" >&2
|
|
27
39
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"禁止通过终端直接写文件,请使用 IDE 工具\"}}"
|
|
40
|
+
write_hook_event "pre-tool-use" "deny" "$cmd" "禁止通过终端直接写文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
28
41
|
exit 2
|
|
29
42
|
fi
|
|
30
|
-
# mv
|
|
31
|
-
if echo "$cmd" | grep -qE '
|
|
32
|
-
echo "⛔ 禁止通过 mv
|
|
33
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"禁止通过 mv
|
|
43
|
+
# cp / mv / touch — 可创建或覆盖文件,无重定向也拦(对齐 core §A 检测5)
|
|
44
|
+
if echo "$cmd" | grep -qE '(^|;|\|\||&&|\|)\s*(cp|mv|touch)\b'; then
|
|
45
|
+
echo "⛔ 禁止通过 cp/mv/touch 操作文件: $cmd。请使用 Write 或 SearchReplace 工具。" >&2
|
|
46
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"禁止通过 cp/mv/touch 操作文件,请使用 IDE 工具\"}}"
|
|
47
|
+
write_hook_event "pre-tool-use" "deny" "$cmd" "禁止通过 cp/mv/touch 操作文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
34
48
|
exit 2
|
|
35
49
|
fi
|
|
36
|
-
# python/node
|
|
37
|
-
if echo "$cmd" | grep -qE '(python3|
|
|
38
|
-
echo "⛔
|
|
39
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"
|
|
50
|
+
# python/node/ruby/perl/php 脚本解释器 — 可写任意文件
|
|
51
|
+
if echo "$cmd" | grep -qE '(^|;|\|\||&&|\|)\s*(python3?|node|ruby|perl|php)(\s|$)'; then
|
|
52
|
+
echo "⛔ 禁止通过脚本解释器直接写文件: $cmd。请使用 Write 或 SearchReplace 工具。" >&2
|
|
53
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"禁止通过脚本解释器直接写文件,请使用 IDE 工具\"}}"
|
|
54
|
+
write_hook_event "pre-tool-use" "deny" "$cmd" "禁止通过脚本解释器直接修改文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
40
55
|
exit 2
|
|
41
56
|
fi
|
|
42
57
|
mark_dev_action 2>/dev/null || true
|
|
@@ -52,7 +67,10 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ]; then
|
|
|
52
67
|
if type detect_active_add >/dev/null 2>&1; then
|
|
53
68
|
state=$(detect_active_add 2>/dev/null || true)
|
|
54
69
|
if [ -z "$state" ]; then
|
|
55
|
-
echo "
|
|
70
|
+
echo "⛔ 正在写入 Plan/Spec/Review 文档但无活跃 ADD Plan——请先执行 add-paradigm" >&2
|
|
71
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Plan/Spec/Review 写入需要活跃 ADD Plan,请先执行 add-paradigm\"}}"
|
|
72
|
+
write_hook_event "pre-tool-use" "deny" "$file_path" "Plan/Spec/Review 写入需活跃 ADD Plan" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
73
|
+
exit 2
|
|
56
74
|
fi
|
|
57
75
|
fi
|
|
58
76
|
fi
|
|
@@ -60,6 +78,7 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ]; then
|
|
|
60
78
|
if echo "$file_path" | grep -qE '\.env$|\.env\.production$|\.env\.local$|credentials|secrets'; then
|
|
61
79
|
echo "⛔ 敏感文件受保护,禁止写入: $file_path" >&2
|
|
62
80
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"敏感文件受保护\"}}"
|
|
81
|
+
write_hook_event "pre-tool-use" "deny" "$file_path" "敏感文件受保护" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
63
82
|
exit 2
|
|
64
83
|
fi
|
|
65
84
|
|
|
@@ -11,6 +11,7 @@ export PROJECT_DIR="${QODER_PROJECT_DIR:-${QODERCN_PROJECT_DIR:-$PWD}}"
|
|
|
11
11
|
source "$HOOK_DIR/lib/common.sh" 2>/dev/null || true
|
|
12
12
|
source "$HOOK_DIR/lib/vocabulary.sh"
|
|
13
13
|
source "$HOOK_DIR/lib/state-detect.sh"
|
|
14
|
+
source "$HOOK_DIR/lib/notify.sh" 2>/dev/null || true
|
|
14
15
|
|
|
15
16
|
# ─── additionalContext 注入(Qoder CN IDE 通过 stdout JSON 注入模型上下文)───
|
|
16
17
|
echo '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"ADD workflow active. Templates preloaded. Use add-paradigm SKILL."}}'
|
|
@@ -68,6 +69,7 @@ if [ -z "$state" ]; then
|
|
|
68
69
|
Step 8: 收敛判断
|
|
69
70
|
如果 add-paradigm SKILL 尚未激活,请先调用它。
|
|
70
71
|
EOF
|
|
72
|
+
write_hook_event "prompt-submit" "deny" "$prompt" "无活跃 ADD Plan 下检测到开发任务" "no-active-plan" "none" 2>/dev/null || true
|
|
71
73
|
exit 2
|
|
72
74
|
fi
|
|
73
75
|
|
|
@@ -77,6 +79,21 @@ step=$(echo "$state" | awk -F'::' '{print $2}')
|
|
|
77
79
|
rounds=$(echo "$state" | awk -F'::' '{print $3}')
|
|
78
80
|
handoff=$(echo "$state" | awk -F'::' '{print $4}')
|
|
79
81
|
add_route=$(echo "$state" | awk -F'::' '{print $5}')
|
|
80
|
-
|
|
82
|
+
add_ctx="[ADD 状态] Plan: ${plan}, 轮次: ${rounds}, Step: ${step}, handoff: ${handoff}"
|
|
83
|
+
|
|
84
|
+
# ─── Hook 治理日报 ───
|
|
85
|
+
HOOK_JSONL="${MAGIC_DIR:-.qoder}/reports/hook-events.jsonl"
|
|
86
|
+
if [ -f "$HOOK_JSONL" ]; then
|
|
87
|
+
TODAY="$(date +%Y-%m-%d)"
|
|
88
|
+
TOTAL=$(grep -c "\"ts\":\"${TODAY}" "$HOOK_JSONL" 2>/dev/null || echo 0)
|
|
89
|
+
NO_PLAN=$(grep "\"ts\":\"${TODAY}" "$HOOK_JSONL" 2>/dev/null | grep -c '"planKeyword":"no-active-plan"' || echo 0)
|
|
90
|
+
if [ "$TOTAL" -gt 0 ] 2>/dev/null; then
|
|
91
|
+
add_ctx="${add_ctx}\n[Hook 治理] 今日拦截: ${TOTAL} 次 | 无 Plan 违规: ${NO_PLAN} 次"
|
|
92
|
+
if [ "$NO_PLAN" -ge 10 ] 2>/dev/null; then
|
|
93
|
+
add_ctx="${add_ctx}\n[Hook ⚠️] 无 Plan 违规已达 ${NO_PLAN} 次(≥10),建议创建 Plan"
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"UserPromptSubmit\",\"additionalContext\":\"${add_ctx}\"}}"
|
|
81
98
|
exit 0
|
|
82
99
|
|
|
@@ -5,6 +5,18 @@ set -euo pipefail
|
|
|
5
5
|
input=$(cat)
|
|
6
6
|
|
|
7
7
|
MAGIC_DIR=".trae"
|
|
8
|
+
PROJECT_DIR="$PWD"
|
|
9
|
+
|
|
10
|
+
# ── Hook 通知: 拦截事件写入 jsonl(旁路,失败不阻断 exit 2)──
|
|
11
|
+
source "${HOOK_DIR}/lib/notify.sh" 2>/dev/null || true
|
|
12
|
+
ACTIVE_PLAN=$(detect_active_add 2>/dev/null || true)
|
|
13
|
+
if [ -n "$ACTIVE_PLAN" ]; then
|
|
14
|
+
PLAN_KEYWORD="${ACTIVE_PLAN%%::*}"
|
|
15
|
+
PLAN_STATUS="active"
|
|
16
|
+
else
|
|
17
|
+
PLAN_KEYWORD="no-active-plan"
|
|
18
|
+
PLAN_STATUS="none"
|
|
19
|
+
fi
|
|
8
20
|
|
|
9
21
|
# DEBUG: dump stdin for investigation
|
|
10
22
|
mkdir -p "$MAGIC_DIR/debug-dump"
|
|
@@ -41,6 +53,7 @@ CONTENT=$(echo "$input" | jq -r '
|
|
|
41
53
|
if [ -z "$CONTENT" ]; then
|
|
42
54
|
echo "⛔ 拒绝:Write 工具未传 file_content,无法校验手写文档格式" >&2
|
|
43
55
|
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"Write 工具未传 file_content,无法校验手写文档。请用 SearchReplace 改写已有文件,或用 Write 工具重试。"}}'
|
|
56
|
+
write_hook_event "doc-format-guard" "deny" "Write" "Write 工具未传 file_content" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
44
57
|
exit 2
|
|
45
58
|
fi
|
|
46
59
|
|
|
@@ -90,6 +103,7 @@ if [ -z "$TEMPLATE_NAME" ]; then
|
|
|
90
103
|
else
|
|
91
104
|
echo "⛔ handoff 文件内容无法识别模板类型(缺 '## 全局元信息' 或 '## 1. 交接前状态'),拒绝写入" >&2
|
|
92
105
|
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"handoff 内容不符合 single/multi 模板规范"}}'
|
|
106
|
+
write_hook_event "doc-format-guard" "deny" "$file_path" "handoff 模板类型无法识别" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
93
107
|
exit 2
|
|
94
108
|
fi
|
|
95
109
|
;;
|
|
@@ -110,6 +124,7 @@ if [ -z "$TEMPLATE_NAME" ]; then
|
|
|
110
124
|
fi
|
|
111
125
|
echo "⛔ 拒绝:无法识别文档类型 (file_path: $file_path),缺少模板匹配规则" >&2
|
|
112
126
|
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"无法识别 ADD 文档类型,请联系管理员更新 doc-format-guard.sh"}}'
|
|
127
|
+
write_hook_event "doc-format-guard" "deny" "$file_path" "无法识别文档类型" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
113
128
|
exit 2
|
|
114
129
|
;;
|
|
115
130
|
esac
|
|
@@ -121,6 +136,7 @@ SCHEMA_FILE="$TEMPLATES_DIR/${TEMPLATE_NAME%.md}.schema.json"
|
|
|
121
136
|
if [ ! -f "$SCHEMA_FILE" ]; then
|
|
122
137
|
echo "⛔ 阻断:模板 ${TEMPLATE_NAME} 缺少对应的 .schema.json 校验规则" >&2
|
|
123
138
|
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"缺少 .schema.json 校验规则文件,禁止无规则放行"}}'
|
|
139
|
+
write_hook_event "doc-format-guard" "deny" "$file_path" "缺少 .schema.json 校验规则" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
124
140
|
exit 2
|
|
125
141
|
fi
|
|
126
142
|
|
|
@@ -227,6 +243,7 @@ if [ -n "$ISSUES" ]; then
|
|
|
227
243
|
echo "⛔ $TEMPLATE_NAME 校验不通过:
|
|
228
244
|
$ISSUES" >&2
|
|
229
245
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"文档格式校验不通过,请对照模板修正\"}}"
|
|
246
|
+
write_hook_event "doc-format-guard" "deny" "$file_path" "文档格式校验不通过" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
230
247
|
exit 2
|
|
231
248
|
fi
|
|
232
249
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# notify.sh — Hook 拦截事件写入 jsonl(零外部依赖,bash 原生)
|
|
3
|
+
# 用法: source 后在 exit 2 前调用 write_hook_event
|
|
4
|
+
#
|
|
5
|
+
# 写入格式(7 字段 jsonl):
|
|
6
|
+
# {"ts":"ISO8601","hook":"...","decision":"deny","cmd":"...","reason":"...","planKeyword":"...","planStatus":"..."}
|
|
7
|
+
#
|
|
8
|
+
# 磁盘管理:
|
|
9
|
+
# - 单文件 ≤256KB,超限轮转为 .old(覆盖,不累积多份)
|
|
10
|
+
# - 总量 ≤512KB(当前 + 一个 .old)
|
|
11
|
+
# - MCP Server 宕机不丢事件,重启后从文件恢复消费
|
|
12
|
+
|
|
13
|
+
write_hook_event() {
|
|
14
|
+
local hook="$1" decision="$2" cmd="$3" reason="$4" plan="${5:-unknown}" status="${6:-none}"
|
|
15
|
+
local dir="${MAGIC_DIR:-.qoder}/reports"
|
|
16
|
+
mkdir -p "$dir" 2>/dev/null || true
|
|
17
|
+
local file="$dir/hook-events.jsonl"
|
|
18
|
+
|
|
19
|
+
# 超过 256KB 自动轮转(262144 bytes)
|
|
20
|
+
if [ -f "$file" ]; then
|
|
21
|
+
local sz
|
|
22
|
+
sz=$(stat -c%s "$file" 2>/dev/null || echo 0)
|
|
23
|
+
[ "$sz" -gt 262144 ] && mv "$file" "${file}.old" 2>/dev/null || true
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
printf '{"ts":"%s","hook":"%s","decision":"%s","cmd":"%s","reason":"%s","planKeyword":"%s","planStatus":"%s"}\n' \
|
|
27
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
28
|
+
"$hook" \
|
|
29
|
+
"$decision" \
|
|
30
|
+
"$cmd" \
|
|
31
|
+
"$reason" \
|
|
32
|
+
"$plan" \
|
|
33
|
+
"$status" >> "$file"
|
|
34
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# pre-tool-use.sh — PreToolUse §A§B(阻断模式,通用适配版)
|
|
3
3
|
# §A: Bash 裸写保护 — 拦截所有绕过 IDE 追踪的文件写操作
|
|
4
|
-
# §B:
|
|
4
|
+
# §B: Write/Edit 写入前置守卫 — Plan/Spec/Review 需要活跃 ADD Plan + 敏感文件保护
|
|
5
5
|
# 治理卡位 #4: 危险命令拦截 / 模板路径兜底 / 写入前置守卫 / 敏感文件保护
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
@@ -13,6 +13,17 @@ PARENT="$(dirname "$HOOK_DIR")"
|
|
|
13
13
|
MAGIC_DIR="$(basename "$PARENT")"
|
|
14
14
|
PROJECT_DIR="$PWD"
|
|
15
15
|
|
|
16
|
+
# ── Hook 通知: 拦截事件写入 jsonl(旁路,失败不阻断 exit 2)──
|
|
17
|
+
source "${HOOK_DIR}/lib/notify.sh" 2>/dev/null || true
|
|
18
|
+
ACTIVE_PLAN=$(detect_active_add 2>/dev/null || true)
|
|
19
|
+
if [ -n "$ACTIVE_PLAN" ]; then
|
|
20
|
+
PLAN_KEYWORD="${ACTIVE_PLAN%%::*}"
|
|
21
|
+
PLAN_STATUS="active"
|
|
22
|
+
else
|
|
23
|
+
PLAN_KEYWORD="no-active-plan"
|
|
24
|
+
PLAN_STATUS="none"
|
|
25
|
+
fi
|
|
26
|
+
|
|
16
27
|
# ── §A 辅助函数: 阻断日志 ──
|
|
17
28
|
_log_block() {
|
|
18
29
|
local rule="$1" cmd="$2"
|
|
@@ -32,7 +43,7 @@ command=$(echo "$input" | jq -r '.tool_input.command // empty')
|
|
|
32
43
|
if [ -n "$command" ]; then
|
|
33
44
|
|
|
34
45
|
# 检测 1: 脚本解释器 — 可写任意文件,无法解析脚本内容做细粒度拦截
|
|
35
|
-
if echo "$command" | grep -qE '
|
|
46
|
+
if echo "$command" | grep -qE '(^|;|\|\||&&|\|)\s*(python3?|node|ruby|perl|php)(\s|$)'; then
|
|
36
47
|
_reason="禁止通过脚本解释器直接修改文件。请使用 Write 或 SearchReplace 工具操作文件。"
|
|
37
48
|
cat >&2 <<'EOF'
|
|
38
49
|
⛔ [ADD PreToolUse §A] 阻断: 禁止通过脚本解释器直接修改文件。
|
|
@@ -47,6 +58,7 @@ if [ -n "$command" ]; then
|
|
|
47
58
|
EOF
|
|
48
59
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
49
60
|
_log_block "脚本解释器" "$command"
|
|
61
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过脚本解释器直接修改文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
50
62
|
exit 2
|
|
51
63
|
fi
|
|
52
64
|
|
|
@@ -61,6 +73,7 @@ EOF
|
|
|
61
73
|
EOF
|
|
62
74
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
63
75
|
_log_block "sed -i" "$command"
|
|
76
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过 sed -i 原地编辑" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
64
77
|
exit 2
|
|
65
78
|
fi
|
|
66
79
|
|
|
@@ -75,6 +88,7 @@ EOF
|
|
|
75
88
|
EOF
|
|
76
89
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
77
90
|
_log_block "重定向" "$command"
|
|
91
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过重定向写入文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
78
92
|
exit 2
|
|
79
93
|
fi
|
|
80
94
|
|
|
@@ -88,11 +102,12 @@ EOF
|
|
|
88
102
|
EOF
|
|
89
103
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
90
104
|
_log_block "tee/dd" "$command"
|
|
105
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过 tee/dd 写入文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
91
106
|
exit 2
|
|
92
107
|
fi
|
|
93
108
|
|
|
94
109
|
# 检测 5: cp / mv / touch — 可创建或覆盖文件
|
|
95
|
-
if echo "$command" | grep -qE '
|
|
110
|
+
if echo "$command" | grep -qE '(^|;|\|\||&&|\|)\s*(cp|mv|touch)\b'; then
|
|
96
111
|
_reason="禁止通过 cp/mv/touch 操作文件。请使用 Write 或 SearchReplace 工具。"
|
|
97
112
|
cat >&2 <<'EOF'
|
|
98
113
|
⛔ [ADD PreToolUse §A] 阻断: 禁止通过 cp/mv/touch 操作文件。
|
|
@@ -101,6 +116,7 @@ EOF
|
|
|
101
116
|
EOF
|
|
102
117
|
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"${_reason}\"}}"
|
|
103
118
|
_log_block "cp/mv/touch" "$command"
|
|
119
|
+
write_hook_event "pre-tool-use" "deny" "$command" "禁止通过 cp/mv/touch 操作文件" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
104
120
|
exit 2
|
|
105
121
|
fi
|
|
106
122
|
|
|
@@ -109,23 +125,37 @@ EOF
|
|
|
109
125
|
exit 0
|
|
110
126
|
fi
|
|
111
127
|
|
|
112
|
-
# ═══════════════ §B:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
# ═══════════════ §B: Write/Edit 文件写入前置守卫 ═══════════════
|
|
129
|
+
# 非 Bash 工具(Write/Edit)的文件写入需要经过:
|
|
130
|
+
# 1. Plan/Spec/Review 文档写入 → 需要活跃 ADD Plan
|
|
131
|
+
# 2. 敏感文件保护 → 阻断写入
|
|
132
|
+
# 3. Dev Action 标记 → 用于 Stop 检查
|
|
133
|
+
# 注意: 不再对 src/**/*.ts 做 Plan 白名单放行,避免绕过 skill/rules 规定的 HITL。
|
|
134
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
135
|
+
if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ]; then
|
|
136
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
|
137
|
+
[ -z "$file_path" ] && exit 0
|
|
138
|
+
|
|
139
|
+
if echo "$file_path" | grep -qE '\.(qoder|claude|add)/(plans|specs|reviews)/'; then
|
|
140
|
+
if type detect_active_add >/dev/null 2>&1; then
|
|
141
|
+
state=$(detect_active_add 2>/dev/null || true)
|
|
142
|
+
if [ -z "$state" ]; then
|
|
143
|
+
echo "⛔ 正在写入 Plan/Spec/Review 文档但无活跃 ADD Plan——请先执行 add-paradigm" >&2
|
|
144
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Plan/Spec/Review 写入需要活跃 ADD Plan,请先执行 add-paradigm\"}}"
|
|
145
|
+
write_hook_event "pre-tool-use" "deny" "$tool_name $file_path" "Plan/Spec/Review 写入需活跃 ADD Plan" "$PLAN_KEYWORD" "$PLAN_STATUS" 2>/dev/null || true
|
|
146
|
+
exit 2
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
121
150
|
|
|
122
|
-
|
|
151
|
+
if echo "$file_path" | grep -qE '\.env$|\.env\.production$|\.env\.local$|credentials|secrets'; then
|
|
152
|
+
echo "⛔ 敏感文件受保护,禁止写入: $file_path" >&2
|
|
153
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"敏感文件受保护\"}}"
|
|
154
|
+
exit 2
|
|
155
|
+
fi
|
|
123
156
|
|
|
124
|
-
|
|
125
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"permissionDecisionReason\":\"相关 ADD Plan 已存在\"}}"
|
|
157
|
+
mark_dev_action 2>/dev/null || true
|
|
126
158
|
exit 0
|
|
127
159
|
fi
|
|
128
160
|
|
|
129
|
-
|
|
130
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"无相关 ADD Plan。请用 AskUserQuestion 询问用户选择 plan-template 或 simple-plan-template 创建 Plan。\"}}"
|
|
131
|
-
exit 2
|
|
161
|
+
exit 0
|