autoworkflow 3.0.1 → 3.1.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/.claude/hooks/audit-runner.sh +191 -0
- package/.claude/hooks/blueprint-generator.sh +253 -0
- package/.claude/hooks/phase-transition.sh +279 -0
- package/.claude/hooks/post-edit.sh +218 -14
- package/.claude/hooks/pre-commit-check.sh +223 -47
- package/.claude/hooks/pre-tool-router.sh +67 -0
- package/.claude/hooks/session-check.sh +328 -41
- package/.claude/settings.json +60 -15
- package/.claude/settings.local.json +5 -1
- package/CLAUDE.md +119 -47
- package/bin/cli.js +9 -1
- package/instructions/CLAUDE.md +22 -0
- package/package.json +1 -1
- package/system/triggers.md +243 -235
|
@@ -1,65 +1,241 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# AutoWorkflow Pre-Commit Check
|
|
3
|
-
# Runs on: PreToolUse for Bash tool
|
|
4
|
-
# Purpose:
|
|
2
|
+
# AutoWorkflow Pre-Commit Gate Check
|
|
3
|
+
# Runs on: PreToolUse for Bash tool (only git commit commands)
|
|
4
|
+
# Purpose: BLOCK commits that violate workflow gates
|
|
5
|
+
#
|
|
6
|
+
# This hook implements the full pre_commit_gate from system/gates.md
|
|
7
|
+
# All 7 checks must pass or the commit is BLOCKED
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
# Colors
|
|
12
|
+
RED='\033[0;31m'
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
CYAN='\033[0;36m'
|
|
16
|
+
BOLD='\033[1m'
|
|
17
|
+
NC='\033[0m'
|
|
18
|
+
|
|
19
|
+
# State directory
|
|
20
|
+
STATE_DIR=".claude/.autoworkflow"
|
|
21
|
+
mkdir -p "$STATE_DIR"
|
|
22
|
+
|
|
23
|
+
# Track errors
|
|
24
|
+
ERRORS=0
|
|
25
|
+
WARNINGS=0
|
|
26
|
+
|
|
27
|
+
# Output formatting
|
|
28
|
+
print_header() {
|
|
29
|
+
echo ""
|
|
30
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
31
|
+
echo -e "${BOLD}AUTOWORKFLOW: PRE-COMMIT GATE${NC}"
|
|
32
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
33
|
+
echo ""
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
print_check() {
|
|
37
|
+
local num=$1
|
|
38
|
+
local name=$2
|
|
39
|
+
local status=$3
|
|
40
|
+
local details=$4
|
|
41
|
+
|
|
42
|
+
if [ "$status" = "pass" ]; then
|
|
43
|
+
echo -e "[${num}/7] ${name}: ${GREEN}✅ PASS${NC} ${details}"
|
|
44
|
+
elif [ "$status" = "fail" ]; then
|
|
45
|
+
echo -e "[${num}/7] ${name}: ${RED}⛔ FAIL${NC}"
|
|
46
|
+
echo -e " └── ${details}"
|
|
47
|
+
else
|
|
48
|
+
echo -e "[${num}/7] ${name}: ${YELLOW}⚠ SKIP${NC} ${details}"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Check 1: TypeScript errors
|
|
53
|
+
check_typescript() {
|
|
54
|
+
if [ -f "package.json" ] && grep -q "typecheck\|tsc" package.json 2>/dev/null; then
|
|
55
|
+
if npm run typecheck 2>&1 | grep -q "error"; then
|
|
56
|
+
TS_ERRORS=$(npm run typecheck 2>&1 | grep -c "error" || echo "0")
|
|
57
|
+
print_check "1" "TypeScript" "fail" "${TS_ERRORS} error(s) found"
|
|
58
|
+
ERRORS=$((ERRORS + 1))
|
|
59
|
+
return 1
|
|
60
|
+
else
|
|
61
|
+
print_check "1" "TypeScript" "pass" "No errors"
|
|
62
|
+
return 0
|
|
63
|
+
fi
|
|
64
|
+
else
|
|
65
|
+
print_check "1" "TypeScript" "skip" "(no typecheck script)"
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Check 2: ESLint warnings
|
|
71
|
+
check_eslint() {
|
|
72
|
+
if [ -f "package.json" ] && grep -q '"lint"' package.json 2>/dev/null; then
|
|
73
|
+
LINT_OUTPUT=$(npm run lint 2>&1 || true)
|
|
74
|
+
if echo "$LINT_OUTPUT" | grep -qE "error|warning"; then
|
|
75
|
+
LINT_ERRORS=$(echo "$LINT_OUTPUT" | grep -c "error" || echo "0")
|
|
76
|
+
LINT_WARNINGS=$(echo "$LINT_OUTPUT" | grep -c "warning" || echo "0")
|
|
77
|
+
print_check "2" "ESLint" "fail" "${LINT_ERRORS} error(s), ${LINT_WARNINGS} warning(s)"
|
|
78
|
+
ERRORS=$((ERRORS + 1))
|
|
79
|
+
return 1
|
|
80
|
+
else
|
|
81
|
+
print_check "2" "ESLint" "pass" "No issues"
|
|
82
|
+
return 0
|
|
83
|
+
fi
|
|
84
|
+
else
|
|
85
|
+
print_check "2" "ESLint" "skip" "(no lint script)"
|
|
86
|
+
return 0
|
|
87
|
+
fi
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Check 3: TODO/FIXME in staged files
|
|
91
|
+
check_todos() {
|
|
92
|
+
if git diff --cached --name-only 2>/dev/null | head -1 | grep -q .; then
|
|
93
|
+
TODO_FILES=$(git diff --cached --name-only 2>/dev/null | xargs grep -l "TODO\|FIXME\|XXX\|HACK" 2>/dev/null || true)
|
|
94
|
+
if [ -n "$TODO_FILES" ]; then
|
|
95
|
+
TODO_COUNT=$(echo "$TODO_FILES" | wc -l | tr -d ' ')
|
|
96
|
+
print_check "3" "TODO/FIXME" "fail" "Found in ${TODO_COUNT} file(s)"
|
|
97
|
+
echo "$TODO_FILES" | while read -r file; do
|
|
98
|
+
echo -e " ${YELLOW}→${NC} $file"
|
|
99
|
+
done
|
|
100
|
+
ERRORS=$((ERRORS + 1))
|
|
101
|
+
return 1
|
|
102
|
+
fi
|
|
19
103
|
fi
|
|
104
|
+
print_check "3" "TODO/FIXME" "pass" "None in staged files"
|
|
20
105
|
return 0
|
|
21
106
|
}
|
|
22
107
|
|
|
23
|
-
# Check
|
|
24
|
-
|
|
25
|
-
if git diff --cached --name-only 2>/dev/null |
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
108
|
+
# Check 4: console.log in staged files
|
|
109
|
+
check_console_logs() {
|
|
110
|
+
if git diff --cached --name-only 2>/dev/null | head -1 | grep -q .; then
|
|
111
|
+
# Exclude test files
|
|
112
|
+
LOG_FILES=$(git diff --cached --name-only 2>/dev/null | grep -v "\.test\.\|\.spec\.\|__tests__" | xargs grep -l "console\.log\|console\.debug\|console\.info" 2>/dev/null || true)
|
|
113
|
+
if [ -n "$LOG_FILES" ]; then
|
|
114
|
+
LOG_COUNT=$(echo "$LOG_FILES" | wc -l | tr -d ' ')
|
|
115
|
+
print_check "4" "console.log" "fail" "Found in ${LOG_COUNT} file(s)"
|
|
116
|
+
echo "$LOG_FILES" | while read -r file; do
|
|
117
|
+
echo -e " ${YELLOW}→${NC} $file"
|
|
118
|
+
done
|
|
119
|
+
ERRORS=$((ERRORS + 1))
|
|
120
|
+
return 1
|
|
121
|
+
fi
|
|
36
122
|
fi
|
|
123
|
+
print_check "4" "console.log" "pass" "None in staged files"
|
|
37
124
|
return 0
|
|
38
125
|
}
|
|
39
126
|
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
127
|
+
# Check 5: UI Enforcement (orphan features)
|
|
128
|
+
check_ui_enforcement() {
|
|
129
|
+
if [ -f "package.json" ] && grep -q '"audit:ui"' package.json 2>/dev/null; then
|
|
130
|
+
AUDIT_OUTPUT=$(npm run audit:ui 2>&1 || true)
|
|
131
|
+
if echo "$AUDIT_OUTPUT" | grep -qi "orphan\|missing ui\|no component"; then
|
|
132
|
+
print_check "5" "UI Enforcement" "fail" "Orphan features detected"
|
|
133
|
+
ERRORS=$((ERRORS + 1))
|
|
134
|
+
return 1
|
|
135
|
+
else
|
|
136
|
+
print_check "5" "UI Enforcement" "pass" "No orphan features"
|
|
137
|
+
return 0
|
|
138
|
+
fi
|
|
139
|
+
else
|
|
140
|
+
print_check "5" "UI Enforcement" "skip" "(no audit:ui script)"
|
|
141
|
+
return 0
|
|
142
|
+
fi
|
|
143
|
+
}
|
|
45
144
|
|
|
46
|
-
#
|
|
47
|
-
|
|
145
|
+
# Check 6: Circular Dependencies
|
|
146
|
+
check_circular_deps() {
|
|
147
|
+
if [ -f "package.json" ] && grep -q '"audit:cycles"' package.json 2>/dev/null; then
|
|
148
|
+
CYCLE_OUTPUT=$(npm run audit:cycles 2>&1 || true)
|
|
149
|
+
if echo "$CYCLE_OUTPUT" | grep -qi "circular\|cycle"; then
|
|
150
|
+
print_check "6" "Circular Deps" "fail" "Cycles detected"
|
|
151
|
+
ERRORS=$((ERRORS + 1))
|
|
152
|
+
return 1
|
|
153
|
+
else
|
|
154
|
+
print_check "6" "Circular Deps" "pass" "No cycles"
|
|
155
|
+
return 0
|
|
156
|
+
fi
|
|
157
|
+
else
|
|
158
|
+
print_check "6" "Circular Deps" "skip" "(no audit:cycles script)"
|
|
159
|
+
return 0
|
|
160
|
+
fi
|
|
161
|
+
}
|
|
48
162
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
163
|
+
# Check 7: Commit message format (conventional commits)
|
|
164
|
+
check_commit_message() {
|
|
165
|
+
# Get the commit message from the staged commit or COMMIT_EDITMSG
|
|
166
|
+
local commit_msg=""
|
|
53
167
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
168
|
+
if [ -f ".git/COMMIT_EDITMSG" ]; then
|
|
169
|
+
commit_msg=$(head -1 .git/COMMIT_EDITMSG)
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
if [ -n "$commit_msg" ]; then
|
|
173
|
+
# Check conventional commit format: type(scope): description
|
|
174
|
+
if echo "$commit_msg" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .+"; then
|
|
175
|
+
print_check "7" "Commit Format" "pass" "Valid conventional commit"
|
|
176
|
+
return 0
|
|
177
|
+
else
|
|
178
|
+
print_check "7" "Commit Format" "fail" "Must be: type(scope): description"
|
|
179
|
+
echo -e " ${YELLOW}→${NC} Got: $commit_msg"
|
|
180
|
+
ERRORS=$((ERRORS + 1))
|
|
181
|
+
return 1
|
|
182
|
+
fi
|
|
183
|
+
else
|
|
184
|
+
print_check "7" "Commit Format" "skip" "(no message yet)"
|
|
185
|
+
return 0
|
|
186
|
+
fi
|
|
187
|
+
}
|
|
58
188
|
|
|
59
|
-
|
|
189
|
+
# Main execution
|
|
190
|
+
main() {
|
|
191
|
+
print_header
|
|
192
|
+
|
|
193
|
+
echo "Checking requirements..."
|
|
60
194
|
echo ""
|
|
61
|
-
|
|
195
|
+
|
|
196
|
+
# Run all checks
|
|
197
|
+
check_typescript || true
|
|
198
|
+
check_eslint || true
|
|
199
|
+
check_todos || true
|
|
200
|
+
check_console_logs || true
|
|
201
|
+
check_ui_enforcement || true
|
|
202
|
+
check_circular_deps || true
|
|
203
|
+
check_commit_message || true
|
|
204
|
+
|
|
62
205
|
echo ""
|
|
206
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
207
|
+
|
|
208
|
+
if [ $ERRORS -gt 0 ]; then
|
|
209
|
+
echo -e "${RED}${BOLD}⛔ GATE BLOCKED${NC} - ${ERRORS} issue(s) must be fixed"
|
|
210
|
+
echo ""
|
|
211
|
+
echo "Fix the issues above before committing."
|
|
212
|
+
echo "The commit has been BLOCKED."
|
|
213
|
+
echo ""
|
|
214
|
+
|
|
215
|
+
# Write block status to state file
|
|
216
|
+
echo "BLOCKED" > "$STATE_DIR/gate-status"
|
|
217
|
+
echo "$ERRORS" > "$STATE_DIR/gate-errors"
|
|
218
|
+
|
|
219
|
+
# EXIT WITH ERROR TO BLOCK THE COMMIT
|
|
220
|
+
exit 1
|
|
221
|
+
else
|
|
222
|
+
echo -e "${GREEN}${BOLD}✅ ALL GATES PASSED${NC}"
|
|
223
|
+
echo ""
|
|
224
|
+
echo "Ready to commit."
|
|
225
|
+
echo ""
|
|
226
|
+
|
|
227
|
+
# Write pass status to state file
|
|
228
|
+
echo "PASSED" > "$STATE_DIR/gate-status"
|
|
229
|
+
echo "0" > "$STATE_DIR/gate-errors"
|
|
230
|
+
|
|
231
|
+
exit 0
|
|
232
|
+
fi
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# Only run if there are staged changes
|
|
236
|
+
if git diff --cached --quiet 2>/dev/null; then
|
|
237
|
+
# No staged changes, skip all checks
|
|
238
|
+
exit 0
|
|
63
239
|
fi
|
|
64
240
|
|
|
65
|
-
|
|
241
|
+
main
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# AutoWorkflow Pre-Tool Router
|
|
3
|
+
# Runs on: PreToolUse for Bash tool
|
|
4
|
+
# Purpose: Route commands to appropriate checks
|
|
5
|
+
#
|
|
6
|
+
# This router determines what checks to run based on the command being executed
|
|
7
|
+
|
|
8
|
+
TOOL_INPUT="$1"
|
|
9
|
+
|
|
10
|
+
# State directory
|
|
11
|
+
STATE_DIR=".claude/.autoworkflow"
|
|
12
|
+
mkdir -p "$STATE_DIR"
|
|
13
|
+
|
|
14
|
+
# Check if this is a git commit command
|
|
15
|
+
is_git_commit() {
|
|
16
|
+
echo "$TOOL_INPUT" | grep -qE "git\s+commit|git\s+.*commit"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Check if this is a git push command
|
|
20
|
+
is_git_push() {
|
|
21
|
+
echo "$TOOL_INPUT" | grep -qE "git\s+push|git\s+.*push"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Check if this is a destructive git command
|
|
25
|
+
is_destructive() {
|
|
26
|
+
echo "$TOOL_INPUT" | grep -qE "git\s+(reset\s+--hard|push\s+--force|push\s+-f|clean\s+-f|checkout\s+\.)"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Route based on command type
|
|
30
|
+
if is_git_commit; then
|
|
31
|
+
# Run full pre-commit gate checks
|
|
32
|
+
exec ./.claude/hooks/pre-commit-check.sh
|
|
33
|
+
|
|
34
|
+
elif is_git_push; then
|
|
35
|
+
# Check if we're pushing to main/master without approval
|
|
36
|
+
if echo "$TOOL_INPUT" | grep -qE "(main|master)"; then
|
|
37
|
+
echo ""
|
|
38
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
39
|
+
echo "⚠️ AUTOWORKFLOW: PUSH TO MAIN DETECTED"
|
|
40
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
41
|
+
echo ""
|
|
42
|
+
echo "You're about to push directly to main/master."
|
|
43
|
+
echo "Consider creating a PR instead."
|
|
44
|
+
echo ""
|
|
45
|
+
# Don't block, just warn
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
exit 0
|
|
49
|
+
|
|
50
|
+
elif is_destructive; then
|
|
51
|
+
echo ""
|
|
52
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
53
|
+
echo "⚠️ AUTOWORKFLOW: DESTRUCTIVE COMMAND DETECTED"
|
|
54
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
55
|
+
echo ""
|
|
56
|
+
echo "Command: $TOOL_INPUT"
|
|
57
|
+
echo ""
|
|
58
|
+
echo "This is a destructive operation that may cause data loss."
|
|
59
|
+
echo "Make sure you have confirmed this with the user."
|
|
60
|
+
echo ""
|
|
61
|
+
# Don't block, just warn - user confirmation is handled by Claude
|
|
62
|
+
exit 0
|
|
63
|
+
|
|
64
|
+
else
|
|
65
|
+
# All other commands pass through
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|