@dtt_siye/atool 1.3.1 → 1.5.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 +97 -214
- package/README.md.atool-backup.20260410_114701 +299 -0
- package/VERSION +1 -1
- package/bin/atool.js +55 -9
- 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/install.sh +14 -4
- package/lib/common.sh +36 -23
- package/lib/compute-importance.sh +73 -0
- package/lib/install-cursor.sh +24 -2
- package/lib/install-hooks.sh +64 -0
- package/lib/install-kiro.sh +26 -2
- package/lib/install-skills.sh +5 -2
- package/lib/pre-scan.sh +13 -1
- package/lib/project-init.sh +28 -9
- package/package.json +1 -1
- package/skills/agent-audit/SKILL.md +180 -0
- package/skills/ai-project-architecture/SKILL.md +33 -534
- package/skills/ai-project-architecture/rules/architecture-validation.md +200 -0
- package/skills/ai-project-architecture/rules/compliance-check.md +83 -0
- package/skills/ai-project-architecture/rules/iron-laws.md +188 -0
- package/skills/ai-project-architecture/rules/migration.md +94 -0
- package/skills/ai-project-architecture/rules/refactoring.md +91 -0
- package/skills/ai-project-architecture/rules/testing.md +249 -0
- package/skills/ai-project-architecture/rules/verification.md +111 -0
- package/skills/architecture-guard/SKILL.md +164 -0
- package/skills/architecture-guard/rules/violation-detection.md +90 -0
- package/skills/atool-init/SKILL.md +24 -4
- package/skills/ci-feedback/SKILL.md +165 -0
- package/skills/project-analyze/SKILL.md +129 -19
- package/skills/project-analyze/phases/phase1-setup.md +76 -5
- package/skills/project-analyze/phases/phase2-understand.md +137 -26
- package/skills/project-analyze/phases/phase2.5-refine.md +32 -23
- package/skills/project-analyze/phases/phase3-graph.md +39 -5
- package/skills/project-analyze/phases/phase4-synthesize.md +17 -1
- package/skills/project-analyze/phases/phase5-export.md +42 -4
- package/skills/project-analyze/prompts/understand-agent.md +156 -298
- package/skills/project-analyze/rules/java.md +69 -1
- package/skills/project-query/SKILL.md +91 -200
- package/skills/project-query/rules/aggregate-stats.md +301 -0
- package/skills/project-query/rules/data-lineage.md +228 -0
- package/skills/project-query/rules/impact-analysis.md +218 -0
- package/skills/project-query/rules/neighborhood.md +234 -0
- package/skills/project-query/rules/node-lookup.md +97 -0
- package/skills/project-query/rules/path-query.md +135 -0
- package/skills/software-architecture/SKILL.md +39 -501
- package/skills/software-architecture/rules/concurrency-ha.md +346 -0
- package/skills/software-architecture/rules/ddd.md +450 -0
- package/skills/software-architecture/rules/decision-workflow.md +155 -0
- package/skills/software-architecture/rules/deployment.md +508 -0
- package/skills/software-architecture/rules/styles.md +232 -0
package/bin/atool.js
CHANGED
|
@@ -61,6 +61,14 @@ function findBashOnWindows() {
|
|
|
61
61
|
function runInstallSh(args) {
|
|
62
62
|
const argList = Array.isArray(args) ? args : [args];
|
|
63
63
|
|
|
64
|
+
// Prepare environment variables
|
|
65
|
+
const env = { ...process.env, ATOOL_ROOT: ROOT_DIR };
|
|
66
|
+
|
|
67
|
+
// Include ATOOL_STACK if set
|
|
68
|
+
if (env.ATOOL_STACK) {
|
|
69
|
+
env.ATOOL_STACK = env.ATOOL_STACK;
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
if (isWindows()) {
|
|
65
73
|
const bash = findBashOnWindows();
|
|
66
74
|
if (!bash) {
|
|
@@ -74,14 +82,14 @@ function runInstallSh(args) {
|
|
|
74
82
|
execFileSync(bash, [INSTALL_SH, ...argList], {
|
|
75
83
|
stdio: 'inherit',
|
|
76
84
|
cwd: ROOT_DIR,
|
|
77
|
-
env:
|
|
85
|
+
env: env,
|
|
78
86
|
});
|
|
79
87
|
} else {
|
|
80
88
|
// macOS / Linux — delegate directly to bash
|
|
81
89
|
execFileSync('bash', [INSTALL_SH, ...argList], {
|
|
82
90
|
stdio: 'inherit',
|
|
83
91
|
cwd: ROOT_DIR,
|
|
84
|
-
env:
|
|
92
|
+
env: env,
|
|
85
93
|
});
|
|
86
94
|
}
|
|
87
95
|
}
|
|
@@ -145,6 +153,38 @@ function cmdInstall(flags) {
|
|
|
145
153
|
runInstallSh(installArgs);
|
|
146
154
|
}
|
|
147
155
|
|
|
156
|
+
function cmdInit(flags) {
|
|
157
|
+
const initArgs = ['--project'];
|
|
158
|
+
|
|
159
|
+
// Extract path argument (only if it's not the value after --stack)
|
|
160
|
+
let pathArg = null;
|
|
161
|
+
let stackValue = null;
|
|
162
|
+
|
|
163
|
+
// Find --stack and its value first
|
|
164
|
+
const stackIndex = flags.indexOf('--stack');
|
|
165
|
+
if (stackIndex !== -1 && stackIndex + 1 < flags.length && !flags[stackIndex + 1].startsWith('--')) {
|
|
166
|
+
stackValue = flags[stackIndex + 1];
|
|
167
|
+
// Remove --stack and its value from consideration for pathArg
|
|
168
|
+
const filteredFlags = flags.filter((flag, index) =>
|
|
169
|
+
index !== stackIndex && index !== stackIndex + 1
|
|
170
|
+
);
|
|
171
|
+
pathArg = filteredFlags.find(flag => !flag.startsWith('--'));
|
|
172
|
+
} else {
|
|
173
|
+
pathArg = flags.find(flag => !flag.startsWith('--'));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (pathArg) {
|
|
177
|
+
initArgs.push(pathArg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle --stack flag
|
|
181
|
+
if (stackValue) {
|
|
182
|
+
process.env.ATOOL_STACK = stackValue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
runInstallSh(initArgs);
|
|
186
|
+
}
|
|
187
|
+
|
|
148
188
|
function cmdUpdate(flags) {
|
|
149
189
|
const updateArgs = ['--update'];
|
|
150
190
|
if (hasFlag(flags, 'yes') || hasFlag(flags, 'y')) {
|
|
@@ -175,20 +215,22 @@ function cmdHelp() {
|
|
|
175
215
|
console.log(`aTool - AI Developer Toolkit v${version}`);
|
|
176
216
|
console.log('');
|
|
177
217
|
console.log('Usage:');
|
|
178
|
-
console.log(' atool
|
|
179
|
-
console.log(' atool update [--yes]');
|
|
180
|
-
console.log(' atool check-updates');
|
|
181
|
-
console.log(' atool version');
|
|
182
|
-
console.log(' atool help');
|
|
218
|
+
console.log(' atool [command] [options]');
|
|
183
219
|
console.log('');
|
|
184
220
|
console.log('Commands:');
|
|
185
221
|
console.log(' install Install aTool to detected AI IDEs (default: --all)');
|
|
222
|
+
console.log(' init [PATH] Initialize a project in current or specified directory');
|
|
186
223
|
console.log(' update Update aTool to the latest version');
|
|
187
224
|
console.log(' check-updates Check installed version info (JSON output)');
|
|
188
225
|
console.log(' version Show current version');
|
|
189
226
|
console.log(' help Show this help message');
|
|
190
227
|
console.log('');
|
|
191
|
-
console.log('
|
|
228
|
+
console.log('Project Initialization:');
|
|
229
|
+
console.log(' atool init Initialize project in current directory');
|
|
230
|
+
console.log(' atool init ./my-app Initialize project in specified directory');
|
|
231
|
+
console.log(' atool init --stack react Initialize with specific tech stack');
|
|
232
|
+
console.log('');
|
|
233
|
+
console.log('Installation Options:');
|
|
192
234
|
console.log(' --all Install to all detected IDEs');
|
|
193
235
|
console.log(' --claude Install to Claude Code CLI only');
|
|
194
236
|
console.log(' --cursor Install to Cursor only');
|
|
@@ -196,8 +238,9 @@ function cmdHelp() {
|
|
|
196
238
|
console.log(' --yes, -y Skip confirmation prompts');
|
|
197
239
|
console.log(' --dry-run Preview mode — no changes made');
|
|
198
240
|
console.log(' --force Overwrite existing configs');
|
|
199
|
-
console.log(' --project PATH Initialize a project');
|
|
241
|
+
console.log(' --project PATH Initialize a project (legacy alias for "atool init")');
|
|
200
242
|
console.log(' --analyze PATH Prepare project for deep analysis');
|
|
243
|
+
console.log(' --stack STACK Specify tech stack (e.g., react, vue, java)');
|
|
201
244
|
}
|
|
202
245
|
|
|
203
246
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
@@ -210,6 +253,9 @@ function main() {
|
|
|
210
253
|
case 'i':
|
|
211
254
|
cmdInstall(flags);
|
|
212
255
|
break;
|
|
256
|
+
case 'init':
|
|
257
|
+
cmdInit(flags);
|
|
258
|
+
break;
|
|
213
259
|
case 'update':
|
|
214
260
|
case 'u':
|
|
215
261
|
cmdUpdate(flags);
|
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>"
|