@dtt_siye/atool 1.3.1 → 1.4.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/VERSION +1 -1
- package/hooks/doc-sync-reminder +4 -4
- package/hooks/hooks-cursor.json +20 -0
- package/hooks/hooks.json +21 -1
- package/hooks/pre-commit +191 -0
- package/hooks/prompt-guard +84 -35
- package/hooks/session-start +34 -12
- package/hooks/task-state-tracker +145 -0
- package/lib/common.sh +36 -23
- package/lib/compute-importance.sh +73 -0
- package/lib/install-cursor.sh +2 -2
- package/lib/install-hooks.sh +64 -0
- package/lib/install-skills.sh +5 -2
- package/lib/pre-scan.sh +11 -1
- package/package.json +1 -1
- package/skills/agent-audit/SKILL.md +180 -0
- package/skills/architecture-guard/SKILL.md +164 -0
- package/skills/architecture-guard/rules/violation-detection.md +90 -0
- package/skills/ci-feedback/SKILL.md +165 -0
- package/skills/project-analyze/SKILL.md +100 -11
- package/skills/project-analyze/phases/phase1-setup.md +15 -1
- package/skills/project-analyze/phases/phase2-understand.md +10 -1
- package/skills/project-analyze/phases/phase2.5-refine.md +32 -23
- package/skills/project-analyze/phases/phase3-graph.md +7 -1
- package/skills/project-analyze/phases/phase4-synthesize.md +17 -1
- package/skills/project-analyze/phases/phase5-export.md +42 -4
- package/skills/project-query/SKILL.md +681 -120
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.4.0
|
package/hooks/doc-sync-reminder
CHANGED
|
@@ -36,8 +36,8 @@ main() {
|
|
|
36
36
|
FILE_PATH=$(printf '%s' "$INPUT" | grep -oE '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' 2>/dev/null | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//' || echo "")
|
|
37
37
|
fi
|
|
38
38
|
|
|
39
|
-
# Only respond to Write/Edit
|
|
40
|
-
if [[ "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" ]]; then
|
|
39
|
+
# Only respond to Write/Edit/MultiEdit
|
|
40
|
+
if [[ "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" && "$TOOL_NAME" != "MultiEdit" ]]; then
|
|
41
41
|
exit 0
|
|
42
42
|
fi
|
|
43
43
|
|
|
@@ -120,9 +120,9 @@ main() {
|
|
|
120
120
|
fi
|
|
121
121
|
fi
|
|
122
122
|
|
|
123
|
-
# If no stale doc detection available,
|
|
123
|
+
# If no stale doc detection available, exit silently (consistent with task-state-tracker fallback)
|
|
124
124
|
if ! $_LIB_FOUND; then
|
|
125
|
-
|
|
125
|
+
exit 0
|
|
126
126
|
fi
|
|
127
127
|
|
|
128
128
|
if ! $STALE; then
|
package/hooks/hooks-cursor.json
CHANGED
|
@@ -22,6 +22,17 @@
|
|
|
22
22
|
]
|
|
23
23
|
}
|
|
24
24
|
],
|
|
25
|
+
"PreToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"matcher": "Bash",
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "bash ~/.cursor/hooks/atool-pre-commit"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
25
36
|
"PostToolUse": [
|
|
26
37
|
{
|
|
27
38
|
"matcher": "Write|Edit",
|
|
@@ -31,6 +42,15 @@
|
|
|
31
42
|
"command": "bash ~/.cursor/hooks/atool-doc-sync-reminder"
|
|
32
43
|
}
|
|
33
44
|
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"matcher": "Write|Edit",
|
|
48
|
+
"hooks": [
|
|
49
|
+
{
|
|
50
|
+
"type": "command",
|
|
51
|
+
"command": "bash ~/.cursor/hooks/atool-task-state-tracker"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
34
54
|
}
|
|
35
55
|
]
|
|
36
56
|
}
|
package/hooks/hooks.json
CHANGED
|
@@ -22,15 +22,35 @@
|
|
|
22
22
|
]
|
|
23
23
|
}
|
|
24
24
|
],
|
|
25
|
+
"PreToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"matcher": "Bash",
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "bash ~/.claude/hooks/atool-pre-commit"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
25
36
|
"PostToolUse": [
|
|
26
37
|
{
|
|
27
|
-
"matcher": "Write|Edit",
|
|
38
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
28
39
|
"hooks": [
|
|
29
40
|
{
|
|
30
41
|
"type": "command",
|
|
31
42
|
"command": "bash ~/.claude/hooks/atool-doc-sync-reminder"
|
|
32
43
|
}
|
|
33
44
|
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
48
|
+
"hooks": [
|
|
49
|
+
{
|
|
50
|
+
"type": "command",
|
|
51
|
+
"command": "bash ~/.claude/hooks/atool-task-state-tracker"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
34
54
|
}
|
|
35
55
|
]
|
|
36
56
|
}
|
package/hooks/pre-commit
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# aTool - hooks/pre-commit
|
|
3
|
+
# PreToolUse hook: enforces quality standards before git commit
|
|
4
|
+
# Checks: TODO/FIXME leftovers, sensitive files, conventional commits, large files
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# Detect which IDE is running this hook
|
|
9
|
+
HOOK_IDE="claude"
|
|
10
|
+
if [[ -n "${CURSOR_PLUGIN_ROOT:-}" ]]; then
|
|
11
|
+
HOOK_IDE="cursor"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Escape string for JSON embedding
|
|
15
|
+
escape_for_json() {
|
|
16
|
+
local s="$1"
|
|
17
|
+
s="${s//\\/\\\\}"
|
|
18
|
+
s="${s//\"/\\\"}"
|
|
19
|
+
s="${s//$'\n'/\\n}"
|
|
20
|
+
s="${s//$'\r'/\\r}"
|
|
21
|
+
s="${s//$'\t'/\\t}"
|
|
22
|
+
printf '%s' "$s"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Read JSON from stdin (Claude Code provides tool input on stdin)
|
|
26
|
+
INPUT=""
|
|
27
|
+
if [[ ! -t 0 ]]; then
|
|
28
|
+
INPUT=$(cat)
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Extract tool_name and command from tool input
|
|
32
|
+
TOOL_NAME=""
|
|
33
|
+
TOOL_INPUT=""
|
|
34
|
+
if command -v jq &>/dev/null && [[ -n "$INPUT" ]]; then
|
|
35
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || echo "")
|
|
36
|
+
TOOL_INPUT=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null || echo "")
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Only intercept git commit commands
|
|
40
|
+
if [[ "$TOOL_NAME" != "Bash" ]]; then
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
if [[ -z "$TOOL_INPUT" ]]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Check if this is a git commit command
|
|
48
|
+
TOOL_INPUT_LOWER=$(printf '%s' "$TOOL_INPUT" | tr '[:upper:]' '[:lower:]')
|
|
49
|
+
IS_GIT_COMMIT=false
|
|
50
|
+
if printf '%s' "$TOOL_INPUT_LOWER" | grep -qE 'git\s+commit'; then
|
|
51
|
+
IS_GIT_COMMIT=true
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
if ! $IS_GIT_COMMIT; then
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# ── Pre-commit checks ─────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
WARNINGS=""
|
|
61
|
+
CHECKS_FAILED=0
|
|
62
|
+
PROJECT_DIR="${PWD:-}"
|
|
63
|
+
|
|
64
|
+
# 1. Check for TODO/FIXME in staged files (AI "lazy" patterns)
|
|
65
|
+
TODO_COUNT=0
|
|
66
|
+
if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
67
|
+
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || echo "")
|
|
68
|
+
if [[ -n "$STAGED_FILES" ]]; then
|
|
69
|
+
for file in $STAGED_FILES; do
|
|
70
|
+
if [[ -f "$PROJECT_DIR/$file" ]]; then
|
|
71
|
+
local_count=$(grep -cE '(TODO|FIXME|HACK|XXX)' "$PROJECT_DIR/$file" 2>/dev/null || echo "0")
|
|
72
|
+
if [[ "$local_count" -gt 0 ]]; then
|
|
73
|
+
TODO_COUNT=$((TODO_COUNT + local_count))
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if [[ "$TODO_COUNT" -gt 0 ]]; then
|
|
81
|
+
WARNINGS+="- Found ${TODO_COUNT} TODO/FIXME/HACK/XXX marker(s) in staged files. Consider resolving before commit.\n"
|
|
82
|
+
CHECKS_FAILED=$((CHECKS_FAILED + 1))
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# 2. Check for sensitive files in staged changes
|
|
86
|
+
SENSITIVE_PATTERNS="(\.env$|\.env\.|credentials|\.pem$|\.key$|secret|\.p12$|\.pfx$|id_rsa|id_ed25519|\.npmrc$|\.pypirc$)"
|
|
87
|
+
SENSITIVE_FOUND=""
|
|
88
|
+
if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
89
|
+
if [[ -n "${STAGED_FILES:-}" ]]; then
|
|
90
|
+
for file in $STAGED_FILES; do
|
|
91
|
+
if printf '%s' "$file" | grep -qE "$SENSITIVE_PATTERNS" 2>/dev/null; then
|
|
92
|
+
if [[ -z "$SENSITIVE_FOUND" ]]; then
|
|
93
|
+
SENSITIVE_FOUND="$file"
|
|
94
|
+
else
|
|
95
|
+
SENSITIVE_FOUND="$SENSITIVE_FOUND, $file"
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
done
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
if [[ -n "$SENSITIVE_FOUND" ]]; then
|
|
103
|
+
WARNINGS+="- BLOCKED: Sensitive file(s) detected in commit: ${SENSITIVE_FOUND}. Do NOT commit secrets.\n"
|
|
104
|
+
CHECKS_FAILED=$((CHECKS_FAILED + 10)) # High severity
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# 3. Check conventional commit message format
|
|
108
|
+
# Note: PreToolUse hook runs BEFORE commit happens, so we can only extract from command line.
|
|
109
|
+
# However, commit message extraction from bash command is unreliable for heredoc format.
|
|
110
|
+
# This check is best-effort only. Use PostToolUse hook for reliable validation after commit.
|
|
111
|
+
COMMIT_MSG=""
|
|
112
|
+
if printf '%s' "$TOOL_INPUT" | grep -qE '\-m'; then
|
|
113
|
+
# Try to extract message after -m flag (only works for simple quoted messages)
|
|
114
|
+
COMMIT_MSG=$(printf '%s' "$TOOL_INPUT" | sed -n 's/.*-m[[:space:]]*\(['"'"'"][^'"'"'"]*['"'"'"]\|"[^"]*"\|\S\+\).*/\1/p' 2>/dev/null | head -1 || echo "")
|
|
115
|
+
# Strip surrounding quotes
|
|
116
|
+
COMMIT_MSG="${COMMIT_MSG#\"}"
|
|
117
|
+
COMMIT_MSG="${COMMIT_MSG%\"}"
|
|
118
|
+
COMMIT_MSG="${COMMIT_MSG#\'}"
|
|
119
|
+
COMMIT_MSG="${COMMIT_MSG%\'}"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Only warn if message extraction succeeded AND it doesn't match pattern
|
|
123
|
+
# (If extraction fails, skip validation — will be checked post-commit by PostToolUse hook)
|
|
124
|
+
if [[ -n "$COMMIT_MSG" ]] && [[ ! "$COMMIT_MSG" =~ \$\(cat ]]; then
|
|
125
|
+
CONVENTIONAL_PATTERN='^(feat|fix|docs|test|refactor|chore|style|perf|build|ci|revert|release)(\(.+\))?:'
|
|
126
|
+
if ! printf '%s' "$COMMIT_MSG" | grep -qE "$CONVENTIONAL_PATTERN" 2>/dev/null; then
|
|
127
|
+
WARNINGS+="- Commit message does not follow Conventional Commits: '${COMMIT_MSG}'\n"
|
|
128
|
+
WARNINGS+=" Expected: feat|fix|docs|test|refactor|chore|style|perf|build|ci|revert|release[(scope)]: description\n"
|
|
129
|
+
# This is a warning, not a hard block
|
|
130
|
+
fi
|
|
131
|
+
else
|
|
132
|
+
# If commit message extraction failed or detected heredoc, add note about post-validation
|
|
133
|
+
if printf '%s' "$TOOL_INPUT" | grep -qE '\-m.*\$\(cat'; then
|
|
134
|
+
WARNINGS+="- INFO: Using heredoc commit message format. Conventional Commits validation will be performed post-commit.\n"
|
|
135
|
+
fi
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# 4. Check for large files (> 1MB) in staged changes
|
|
139
|
+
LARGE_FILES=""
|
|
140
|
+
if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
141
|
+
if [[ -n "${STAGED_FILES:-}" ]]; then
|
|
142
|
+
for file in $STAGED_FILES; do
|
|
143
|
+
if [[ -f "$PROJECT_DIR/$file" ]]; then
|
|
144
|
+
file_size=$(wc -c < "$PROJECT_DIR/$file" 2>/dev/null || echo "0")
|
|
145
|
+
# 1MB = 1048576 bytes
|
|
146
|
+
if [[ "$file_size" -gt 1048576 ]]; then
|
|
147
|
+
size_mb=$((file_size / 1048576))
|
|
148
|
+
if [[ -z "$LARGE_FILES" ]]; then
|
|
149
|
+
LARGE_FILES="$file (${size_mb}MB)"
|
|
150
|
+
else
|
|
151
|
+
LARGE_FILES="$LARGE_FILES, $file (${size_mb}MB)"
|
|
152
|
+
fi
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
if [[ -n "$LARGE_FILES" ]]; then
|
|
160
|
+
WARNINGS+="- WARNING: Large file(s) in commit: ${LARGE_FILES}. Consider using .gitattributes or Git LFS.\n"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# ── Output ────────────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
if [[ "$CHECKS_FAILED" -gt 0 ]] || [[ -n "$WARNINGS" ]]; then
|
|
166
|
+
SEVERITY="WARNING"
|
|
167
|
+
if [[ "$CHECKS_FAILED" -ge 10 ]]; then
|
|
168
|
+
SEVERITY="BLOCKED"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
_MSG="<ATOOL-PRE-COMMIT-CHECK>\n"
|
|
172
|
+
_MSG+="${SEVERITY}: Pre-commit quality checks found issues:\n\n"
|
|
173
|
+
_MSG+="$WARNINGS"
|
|
174
|
+
_MSG+="\n"
|
|
175
|
+
|
|
176
|
+
if [[ "$SEVERITY" == "BLOCKED" ]]; then
|
|
177
|
+
_MSG+="**This commit is BLOCKED.** Fix the critical issues above before proceeding.\n"
|
|
178
|
+
else
|
|
179
|
+
_MSG+="Please review the warnings above. You may proceed if they are acceptable, but consider addressing them.\n"
|
|
180
|
+
fi
|
|
181
|
+
_MSG+="</ATOOL-PRE-COMMIT-CHECK>"
|
|
182
|
+
|
|
183
|
+
if [[ "$HOOK_IDE" == "cursor" ]]; then
|
|
184
|
+
_ESCAPED=$(escape_for_json "$_MSG")
|
|
185
|
+
printf '{\n "hookSpecificOutput": {\n "hookEventName": "PreToolUse",\n "additionalContext": "%s"\n }\n}\n' "$_ESCAPED"
|
|
186
|
+
else
|
|
187
|
+
printf '%b' "$_MSG"
|
|
188
|
+
fi
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
exit 0
|
package/hooks/prompt-guard
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# aTool - hooks/prompt-guard
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# UserPromptSubmit hook: reminds AI to clarify before implementing
|
|
4
|
+
# v2: Tightened skip conditions, structured template injection for Tier 2+
|
|
5
5
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
@@ -57,45 +57,41 @@ if printf '%s' "$PROMPT" | LC_ALL=C grep -q '[一-龥]' 2>/dev/null; then
|
|
|
57
57
|
HAS_CJK=true
|
|
58
58
|
fi
|
|
59
59
|
|
|
60
|
-
# 1. Short messages
|
|
61
|
-
|
|
60
|
+
# 1. Short messages — TIGHTENED: lower thresholds
|
|
61
|
+
# Old: CJK < 8, Latin < 20
|
|
62
|
+
# New: CJK < 6, Latin < 15 (pure short replies like "ok", "好")
|
|
63
|
+
if $HAS_CJK && [[ "$PROMPT_LEN" -lt 6 ]]; then
|
|
62
64
|
exit 0
|
|
63
|
-
elif ! $HAS_CJK && [[ "$PROMPT_LEN" -lt
|
|
65
|
+
elif ! $HAS_CJK && [[ "$PROMPT_LEN" -lt 15 ]]; then
|
|
64
66
|
exit 0
|
|
65
67
|
fi
|
|
66
68
|
|
|
67
|
-
# 2.
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
# 2. Pure confirmations only — TIGHTENED: strict single-word matches
|
|
70
|
+
# Old: included multi-word phrases like "go ahead", "pls", "please"
|
|
71
|
+
# New: only exact single-word affirmative replies
|
|
72
|
+
PURE_CONFIRMATIONS="^(yes|ok|okay|好的|继续|对|嗯|是的|行|可以|sure|yep|yeah|done|go|next)$"
|
|
73
|
+
if printf '%s' "$PROMPT_LOWER" | grep -qE "$PURE_CONFIRMATIONS" 2>/dev/null; then
|
|
70
74
|
exit 0
|
|
71
75
|
fi
|
|
72
76
|
|
|
73
|
-
# 3.
|
|
74
|
-
if printf '%s' "$PROMPT" | grep -qE '[??]$' 2>/dev/null; then
|
|
75
|
-
exit 0
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
# 4. Meta-commands (non-implementation requests)
|
|
79
|
-
# Note: Ambiguous words like "build", "test", "deploy" are NOT listed here
|
|
80
|
-
# because they could be feature requests (e.g., "build a dashboard")
|
|
81
|
-
META_COMMANDS="^(commit|git |run |npm |yarn |pnpm |bun |cargo |go run|python |pytest|jest|vitest|explain|diff |show |list |check |lint |format |log |tail |grep |find |cat |head )"
|
|
82
|
-
if printf '%s' "$PROMPT_LOWER" | grep -qE "$META_COMMANDS" 2>/dev/null; then
|
|
83
|
-
exit 0
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
# 5. Exit signals (user wants to skip clarification)
|
|
77
|
+
# 3. Exit signals (user explicitly wants to skip clarification) — KEPT as-is
|
|
87
78
|
EXIT_SIGNALS="(别问了|不要问|别再问|直接做|直接实现|直接开始|不用确认|跳过提问|不要确认|just do it|stop asking|no questions|don.t ask|skip asking|go ahead and implement)"
|
|
88
79
|
if printf '%s' "$PROMPT_LOWER" | grep -qE "$EXIT_SIGNALS" 2>/dev/null; then
|
|
89
80
|
exit 0
|
|
90
81
|
fi
|
|
91
82
|
|
|
92
|
-
# ──
|
|
83
|
+
# ── REMOVED: Old skip conditions that were too broad ───────────────────────
|
|
84
|
+
# OLD: "Questions ending with ?" → REMOVED (questions like "能帮我实现一个搜索功能吗?" should trigger)
|
|
85
|
+
# OLD: "Meta-commands (commit, run, npm, explain...)" → REMOVED
|
|
86
|
+
# ("build a dashboard" starts with "build" but is a feature request)
|
|
87
|
+
# ("run the migration to add new user fields" is an implementation request)
|
|
88
|
+
|
|
89
|
+
# ── Classify task complexity ──────────────────────────────────────────────
|
|
93
90
|
|
|
94
91
|
# Count words: for CJK text (no spaces), count CJK chars as words;
|
|
95
92
|
# for Latin text, count whitespace-separated tokens
|
|
96
93
|
WORD_COUNT=0
|
|
97
94
|
if $HAS_CJK; then
|
|
98
|
-
# CJK: count CJK characters (use LC_ALL=C for locale safety)
|
|
99
95
|
CJK_COUNT=$(printf '%s' "$PROMPT" | LC_ALL=C grep -o '[一-龥]' 2>/dev/null | wc -l | tr -d '[:space:]')
|
|
100
96
|
WORD_COUNT=$((CJK_COUNT))
|
|
101
97
|
else
|
|
@@ -104,25 +100,78 @@ else
|
|
|
104
100
|
done
|
|
105
101
|
fi
|
|
106
102
|
|
|
107
|
-
#
|
|
108
|
-
|
|
103
|
+
# Detect implementation intent keywords
|
|
104
|
+
# Extended with common natural language patterns (CJK + English)
|
|
105
|
+
IMPLEMENT_KEYWORDS="(开发|创建|新增|实现|添加|加个|做一个|写一个|重构|优化|修改|调整|修复|集成|迁移|设计|拆分|改造|搭建|编写|扩展|帮我|需要|能不能|可以|请|麻烦|给我|加上|看看|想要|build|create|add|implement|make|develop|design|feature|refactor|extract|integrate|support|handle|fix|migrate|modify|update|change|enhance|improve|help.me|show.me|please|create.a|build.a|write.a|let.me|could.you|can.you)"
|
|
106
|
+
|
|
107
|
+
HAS_IMPLEMENT=false
|
|
109
108
|
LENGTH_OK=false
|
|
110
|
-
|
|
109
|
+
|
|
110
|
+
if $HAS_CJK && [[ "$PROMPT_LEN" -ge 6 ]]; then
|
|
111
111
|
LENGTH_OK=true
|
|
112
|
-
elif ! $HAS_CJK && [[ "$PROMPT_LEN" -ge
|
|
112
|
+
elif ! $HAS_CJK && [[ "$PROMPT_LEN" -ge 15 ]]; then
|
|
113
113
|
LENGTH_OK=true
|
|
114
114
|
fi
|
|
115
115
|
|
|
116
|
-
if
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
if printf '%s' "$PROMPT_LOWER" | grep -qE "$IMPLEMENT_KEYWORDS" 2>/dev/null; then
|
|
117
|
+
HAS_IMPLEMENT=true
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# ── Determine tier and output ─────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
if $LENGTH_OK && $HAS_IMPLEMENT && [[ "$WORD_COUNT" -gt 3 ]]; then
|
|
123
|
+
# Determine complexity tier
|
|
124
|
+
TIER=0
|
|
125
|
+
|
|
126
|
+
# Tier 3 indicators: architecture-level, multi-module, system-wide
|
|
127
|
+
TIER3_KEYWORDS="(架构|系统|整体|全面|重新设计|从零|微服务|拆分|architecture|system.redesign|from scratch|microservice|monolith|break down|overhaul)"
|
|
128
|
+
# Tier 2 indicators: new feature, multi-step, unclear scope
|
|
129
|
+
TIER2_KEYWORDS="(功能|模块|页面|组件|接口|服务|支持|feature|module|page|component|endpoint|service|support|workflow|pipeline|dashboard|integration)"
|
|
130
|
+
|
|
131
|
+
if printf '%s' "$PROMPT_LOWER" | grep -qE "$TIER3_KEYWORDS" 2>/dev/null; then
|
|
132
|
+
TIER=3
|
|
133
|
+
elif printf '%s' "$PROMPT_LOWER" | grep -qE "$TIER2_KEYWORDS" 2>/dev/null; then
|
|
134
|
+
TIER=2
|
|
135
|
+
else
|
|
136
|
+
TIER=1
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Fallback tier escalation: if message has pronouns + concrete request context, bump tier
|
|
140
|
+
# This catches natural language requests without explicit keywords
|
|
141
|
+
if [[ "$TIER" -eq 1 ]] && [[ "$PROMPT_LEN" -gt 30 ]]; then
|
|
142
|
+
if printf '%s' "$PROMPT_LOWER" | grep -qE "(我|my|我们|our|帮|help|想|want|需要|need|能|can|可以|able)" 2>/dev/null; then
|
|
143
|
+
# Check if there are specific code/file references (suggests implementation request)
|
|
144
|
+
if printf '%s' "$PROMPT" | grep -qE "(\\.\\w+|/\\w+|\\{\\w+\\}|function|class|method|endpoint)" 2>/dev/null; then
|
|
145
|
+
TIER=2
|
|
146
|
+
fi
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# Build tier-appropriate reminder
|
|
151
|
+
if [[ "$TIER" -ge 2 ]]; then
|
|
152
|
+
# Tier 2+: Structured template — require analysis before implementation
|
|
153
|
+
_REMINDER="<system-reminder>
|
|
154
|
+
**TIER ${TIER} TASK DETECTED** — This is a ${TIER}-level task. Before writing ANY code:
|
|
155
|
+
|
|
156
|
+
1. **Analyze** — Read relevant source files first (CLAUDE.md, package.json, existing patterns)
|
|
157
|
+
2. **Classify** — Confirm complexity: Tier ${TIER} = $([ "$TIER" -eq 2 ] && echo "3-5 clarification questions" || echo "delegate to /brainstorming")
|
|
158
|
+
3. **Clarify** — Ask context-aware questions about: $([ "$TIER" -eq 2 ] && echo "scope, edge cases, existing patterns, acceptance criteria" || echo "architecture, constraints, trade-offs, migration strategy")
|
|
159
|
+
4. **Plan** — Write implementation plan BEFORE touching code
|
|
160
|
+
|
|
161
|
+
Do NOT jump to implementation. Read first, ask second, plan third, code last.
|
|
162
|
+
See /clarify-before-build for full methodology.
|
|
163
|
+
</system-reminder>"
|
|
164
|
+
else
|
|
165
|
+
# Tier 1: Lightweight reminder
|
|
166
|
+
_REMINDER='<system-reminder>
|
|
119
167
|
Before implementing, classify task complexity and clarify requirements if needed:
|
|
120
|
-
- Tier 0 (direct): single-value change
|
|
121
|
-
- Tier 1 (1-2 questions): small change with ambiguity
|
|
122
|
-
- Tier 2 (3-5 questions): new feature
|
|
123
|
-
- Tier 3: architecture-level
|
|
168
|
+
- Tier 0 (direct): single-value change, typo, format fix -> just do it
|
|
169
|
+
- Tier 1 (1-2 questions): small change with ambiguity -> ask key questions
|
|
170
|
+
- Tier 2 (3-5 questions): new feature, medium complexity -> structured clarification
|
|
171
|
+
- Tier 3: architecture-level, vague requirements -> delegate to /brainstorming
|
|
124
172
|
See /clarify-before-build for full methodology.
|
|
125
173
|
</system-reminder>'
|
|
174
|
+
fi
|
|
126
175
|
|
|
127
176
|
if [[ "$HOOK_IDE" == "cursor" ]]; then
|
|
128
177
|
_ESCAPED=$(escape_for_json "$_REMINDER")
|
package/hooks/session-start
CHANGED
|
@@ -245,26 +245,48 @@ if [[ -n "$DOC_SYNC" ]]; then
|
|
|
245
245
|
INJECTION+=$'\n\n'
|
|
246
246
|
fi
|
|
247
247
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
INJECTION+="
|
|
251
|
-
INJECTION+=$'\n'
|
|
252
|
-
INJECTION+="
|
|
248
|
+
# Only inject clarify rule if in a code project (detected by presence of common project files)
|
|
249
|
+
if [[ -f "package.json" || -f "pom.xml" || -f "go.mod" || -f "Cargo.toml" || -f "requirements.txt" || -f "build.gradle" || -f ".gradle" || -f "setup.py" || -f "Gemfile" ]]; then
|
|
250
|
+
INJECTION+="<ATOOL-CLARIFY-RULE>"
|
|
251
|
+
INJECTION+=$'\n'
|
|
252
|
+
INJECTION+="Before implementing ANY feature, first classify task complexity:"
|
|
253
|
+
INJECTION+=$'\n'
|
|
254
|
+
INJECTION+="Tier 0 (direct): single-value change, typo, format fix -> just do it"
|
|
255
|
+
INJECTION+=$'\n'
|
|
256
|
+
INJECTION+="Tier 1 (1-2 questions): small change with ambiguity -> ask key questions"
|
|
257
|
+
INJECTION+=$'\n'
|
|
258
|
+
INJECTION+="Tier 2 (3-5 questions): new feature, medium complexity -> structured clarification"
|
|
259
|
+
INJECTION+=$'\n'
|
|
260
|
+
INJECTION+="Tier 3: architecture-level, vague requirements -> delegate to /brainstorming"
|
|
261
|
+
INJECTION+=$'\n'
|
|
262
|
+
INJECTION+="Rules: Read project files BEFORE asking (CLAUDE.md, package.json, source code). Never assume installed libs or existing patterns. Ask context-aware questions, not obvious ones."
|
|
263
|
+
INJECTION+=$'\n'
|
|
264
|
+
INJECTION+="Exit: If user says '别问了'/'直接做'/'just do it'/'stop asking' -> skip all questions, Tier 0."
|
|
265
|
+
INJECTION+=$'\n'
|
|
266
|
+
INJECTION+="See /clarify-before-build for full methodology."
|
|
267
|
+
INJECTION+=$'\n'
|
|
268
|
+
INJECTION+="</ATOOL-CLARIFY-RULE>"
|
|
269
|
+
INJECTION+=$'\n'
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
# Inject language rule (always, for all projects)
|
|
273
|
+
INJECTION+="<ATOOL-LANGUAGE-RULE>"
|
|
253
274
|
INJECTION+=$'\n'
|
|
254
|
-
INJECTION+="
|
|
275
|
+
INJECTION+="语言要求 (Language Requirement):"
|
|
255
276
|
INJECTION+=$'\n'
|
|
256
|
-
INJECTION+="
|
|
277
|
+
INJECTION+="- 项目文档 (docs/, README, 架构指南): 必须中文"
|
|
257
278
|
INJECTION+=$'\n'
|
|
258
|
-
INJECTION+="
|
|
279
|
+
INJECTION+="- SKILL.md: 保持英文(AI 指令文件)"
|
|
259
280
|
INJECTION+=$'\n'
|
|
260
|
-
INJECTION+="
|
|
281
|
+
INJECTION+="- Commit message: 推荐中文"
|
|
261
282
|
INJECTION+=$'\n'
|
|
262
|
-
INJECTION+="
|
|
283
|
+
INJECTION+="- 代码注释: 可中文或英文(跟随代码风格)"
|
|
263
284
|
INJECTION+=$'\n'
|
|
264
|
-
INJECTION+="
|
|
285
|
+
INJECTION+="如果误用英文写项目文档,请用 /doc-coauthoring 重写为中文。"
|
|
265
286
|
INJECTION+=$'\n'
|
|
266
|
-
INJECTION+="</ATOOL-
|
|
287
|
+
INJECTION+="</ATOOL-LANGUAGE-RULE>"
|
|
267
288
|
INJECTION+=$'\n'
|
|
289
|
+
|
|
268
290
|
INJECTION+="</ATOOL-SESSION>"
|
|
269
291
|
INJECTION+=$'\n'
|
|
270
292
|
INJECTION+="</EXTREMELY_IMPORTANT>"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# aTool - hooks/task-state-tracker
|
|
3
|
+
# PostToolUse hook: tracks session-level task state for feedback loop
|
|
4
|
+
# Records: modified files, doc staleness, verification status, pending actions
|
|
5
|
+
# State file: .claude/task-state.json (session-scoped, gitignored)
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# Escape string for JSON embedding
|
|
10
|
+
escape_for_json() {
|
|
11
|
+
local s="$1"
|
|
12
|
+
s="${s//\\/\\\\}"
|
|
13
|
+
s="${s//\"/\\\"}"
|
|
14
|
+
s="${s//$'\n'/\\n}"
|
|
15
|
+
s="${s//$'\r'/\\r}"
|
|
16
|
+
s="${s//$'\t'/\\t}"
|
|
17
|
+
printf '%s' "$s"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# ── Read tool input ───────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
INPUT=""
|
|
23
|
+
if [[ ! -t 0 ]]; then
|
|
24
|
+
INPUT=$(cat)
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
TOOL_NAME=""
|
|
28
|
+
FILE_PATH=""
|
|
29
|
+
if command -v jq &>/dev/null && [[ -n "$INPUT" ]]; then
|
|
30
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || echo "")
|
|
31
|
+
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null || echo "")
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Only respond to Write/Edit
|
|
35
|
+
if [[ "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" ]]; then
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
if [[ -z "$FILE_PATH" ]]; then
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# ── Source file detection ────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
FILE_EXT="${FILE_PATH##*.}"
|
|
45
|
+
FILE_BASE=$(basename "$FILE_PATH")
|
|
46
|
+
|
|
47
|
+
# Skip non-source files (same logic as doc-sync-reminder)
|
|
48
|
+
case "$FILE_EXT" in
|
|
49
|
+
md|markdown|txt|rst) exit 0 ;;
|
|
50
|
+
json|yaml|yml|toml|xml) exit 0 ;;
|
|
51
|
+
css|scss|less|sass|styl) exit 0 ;;
|
|
52
|
+
lock|map|log) exit 0 ;;
|
|
53
|
+
svg|png|jpg|jpeg|gif|ico|webp|ttf|woff|woff2|eot) exit 0 ;;
|
|
54
|
+
esac
|
|
55
|
+
case "$FILE_BASE" in
|
|
56
|
+
.*|*.config.*) exit 0 ;;
|
|
57
|
+
esac
|
|
58
|
+
case "$FILE_EXT" in
|
|
59
|
+
ts|tsx|js|jsx|vue|svelte|html|rs|py|go|java|kt|kts|swift|dart|ets|sh|bash) ;;
|
|
60
|
+
*) exit 0 ;;
|
|
61
|
+
esac
|
|
62
|
+
|
|
63
|
+
# ── State management ──────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
# Require jq
|
|
66
|
+
if ! command -v jq &>/dev/null; then
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
NOW=$(date +%s 2>/dev/null || echo "0")
|
|
71
|
+
STATE_FILE=".claude/task-state.json"
|
|
72
|
+
MAX_TRACKED_FILES=50
|
|
73
|
+
|
|
74
|
+
# Initialize state file if needed
|
|
75
|
+
if [[ ! -f "$STATE_FILE" ]]; then
|
|
76
|
+
mkdir -p ".claude" 2>/dev/null || true
|
|
77
|
+
printf '{"session_id":"","modified_files":[],"docs_stale":false,"verification_passed":false,"pending_actions":[],"architecture_violations":0,"created_at":%s,"updated_at":%s}\n' "$NOW" "$NOW" > "$STATE_FILE"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Read current state
|
|
81
|
+
STATE=$(cat "$STATE_FILE" 2>/dev/null || echo "{}")
|
|
82
|
+
|
|
83
|
+
# Update: add file, set docs_stale=true, reset verification, update timestamp
|
|
84
|
+
NEW_STATE=$(printf '%s' "$STATE" | jq \
|
|
85
|
+
--arg fp "$FILE_PATH" \
|
|
86
|
+
--argjson now "$NOW" \
|
|
87
|
+
'
|
|
88
|
+
# Add file if not already tracked
|
|
89
|
+
if (.modified_files | map(select(.path == $fp)) | length) == 0 then
|
|
90
|
+
.modified_files += [{"path": $fp, "modified_at": $now}]
|
|
91
|
+
else
|
|
92
|
+
.modified_files = [.modified_files[] | if .path == $fp then .modified_at = $now else . end]
|
|
93
|
+
end
|
|
94
|
+
# Cap modified files to 50
|
|
95
|
+
| .modified_files = (.modified_files | sort_by(-.modified_at) | .[0:50])
|
|
96
|
+
# Mark docs as stale
|
|
97
|
+
| .docs_stale = true
|
|
98
|
+
# Reset verification since we made changes
|
|
99
|
+
| .verification_passed = false
|
|
100
|
+
# Ensure pending_actions includes doc update
|
|
101
|
+
| if (.pending_actions | index("update_docs") | not) then
|
|
102
|
+
.pending_actions += ["update_docs"]
|
|
103
|
+
else . end
|
|
104
|
+
| .updated_at = $now
|
|
105
|
+
')
|
|
106
|
+
|
|
107
|
+
printf '%s' "$NEW_STATE" | jq '.' > "$STATE_FILE" 2>/dev/null || true
|
|
108
|
+
|
|
109
|
+
# ── Output feedback at milestones ─────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
FILE_COUNT=$(jq '.modified_files | length' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
112
|
+
|
|
113
|
+
_MILESTONE=""
|
|
114
|
+
# Milestone check: for every 5 files >= 10, show progress update
|
|
115
|
+
if (( FILE_COUNT >= 10 && FILE_COUNT % 5 == 0 )); then
|
|
116
|
+
if (( FILE_COUNT == 10 )); then
|
|
117
|
+
_MILESTONE="<ATOOL-TASK-PROGRESS>
|
|
118
|
+
Progress check: You have modified ${FILE_COUNT} source files. This is substantial.
|
|
119
|
+
- Consider breaking your work into smaller, verifiable units
|
|
120
|
+
- Docs are STALE and need updating
|
|
121
|
+
- Run /verification-before-completion before claiming done
|
|
122
|
+
</ATOOL-TASK-PROGRESS>"
|
|
123
|
+
else
|
|
124
|
+
_MILESTONE="<ATOOL-TASK-PROGRESS>
|
|
125
|
+
Progress check: You have modified ${FILE_COUNT} source files.
|
|
126
|
+
- This is a large refactoring. Consider intermediate commits/verification
|
|
127
|
+
- Docs are STALE and need updating
|
|
128
|
+
- Run /verification-before-completion to validate progress
|
|
129
|
+
</ATOOL-TASK-PROGRESS>"
|
|
130
|
+
fi
|
|
131
|
+
elif (( FILE_COUNT == 5 )); then
|
|
132
|
+
_MILESTONE="<ATOOL-TASK-PROGRESS>
|
|
133
|
+
Progress check: You have modified ${FILE_COUNT} source files.
|
|
134
|
+
- Docs are STALE and need updating
|
|
135
|
+
- Verification has NOT been run since last changes
|
|
136
|
+
Remember: Run /verification-before-completion before claiming done.
|
|
137
|
+
</ATOOL-TASK-PROGRESS>"
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
if [[ -n "$_MILESTONE" ]]; then
|
|
141
|
+
_ESCAPED=$(escape_for_json "$_MILESTONE")
|
|
142
|
+
printf '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"%s"}}\n' "$_ESCAPED"
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
exit 0
|