autoworkflow 3.0.0 → 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.
@@ -1,60 +1,241 @@
1
1
  #!/bin/bash
2
- # AutoWorkflow Pre-Commit Check Hook
3
- # Runs on: PreToolUse for Bash tool when command contains "git commit"
4
- # Purpose: Block commits with TODO/FIXME/console.log
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
- # Get the command being run
7
- COMMAND="$1"
9
+ set -e
8
10
 
9
- # Check if this is a git commit command
10
- if echo "$COMMAND" | grep -q "git commit"; then
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() {
11
29
  echo ""
12
- echo "=================================================="
13
- echo "AUTOWORKFLOW: PRE-COMMIT CHECK"
14
- echo "=================================================="
30
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
31
+ echo -e "${BOLD}AUTOWORKFLOW: PRE-COMMIT GATE${NC}"
32
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
33
+ echo ""
34
+ }
15
35
 
16
- ERRORS=0
36
+ print_check() {
37
+ local num=$1
38
+ local name=$2
39
+ local status=$3
40
+ local details=$4
17
41
 
18
- # Check for TODO/FIXME in staged files
19
- if git diff --cached --name-only 2>/dev/null | xargs grep -l "TODO\|FIXME\|XXX\|HACK" 2>/dev/null; then
20
- echo ""
21
- echo "BLOCKED: TODO/FIXME comments found in staged files"
22
- echo "Remove all TODO/FIXME comments before committing."
23
- ERRORS=$((ERRORS + 1))
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}"
24
49
  fi
50
+ }
25
51
 
26
- # Check for console.log in staged files (excluding test files)
27
- if 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; then
28
- echo ""
29
- echo "BLOCKED: console.log statements found in staged files"
30
- echo "Remove debug logs before committing."
31
- ERRORS=$((ERRORS + 1))
32
- fi
33
-
34
- # Check commit message format (if provided)
35
- if echo "$COMMAND" | grep -q '\-m'; then
36
- MSG=$(echo "$COMMAND" | sed -n 's/.*-m ["\x27]\([^"\x27]*\)["\x27].*/\1/p')
37
- if [ -n "$MSG" ]; then
38
- # Check conventional commit format
39
- if ! echo "$MSG" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9-]+\))?: .+"; then
40
- echo ""
41
- echo "BLOCKED: Invalid commit message format"
42
- echo "Use: type(scope): description"
43
- echo "Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
44
- ERRORS=$((ERRORS + 1))
45
- fi
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
46
83
  fi
84
+ else
85
+ print_check "2" "ESLint" "skip" "(no lint script)"
86
+ return 0
47
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
103
+ fi
104
+ print_check "3" "TODO/FIXME" "pass" "None in staged files"
105
+ return 0
106
+ }
107
+
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
122
+ fi
123
+ print_check "4" "console.log" "pass" "None in staged files"
124
+ return 0
125
+ }
126
+
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
+ }
144
+
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
+ }
162
+
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=""
167
+
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
+ }
188
+
189
+ # Main execution
190
+ main() {
191
+ print_header
192
+
193
+ echo "Checking requirements..."
194
+ echo ""
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
+
205
+ echo ""
206
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
48
207
 
49
208
  if [ $ERRORS -gt 0 ]; then
209
+ echo -e "${RED}${BOLD}⛔ GATE BLOCKED${NC} - ${ERRORS} issue(s) must be fixed"
50
210
  echo ""
51
- echo "Fix the above issues before committing."
52
- echo "=================================================="
211
+ echo "Fix the issues above before committing."
212
+ echo "The commit has been BLOCKED."
53
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
54
221
  else
222
+ echo -e "${GREEN}${BOLD}✅ ALL GATES PASSED${NC}"
55
223
  echo ""
56
- echo "All pre-commit checks passed."
57
- echo "=================================================="
224
+ echo "Ready to commit."
58
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
59
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
60
239
  fi
240
+
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