claude-git-hooks 1.5.4 → 2.0.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/CHANGELOG.md +89 -1
- package/README.md +130 -35
- package/bin/claude-hooks +253 -287
- package/lib/hooks/pre-commit.js +335 -0
- package/lib/hooks/prepare-commit-msg.js +283 -0
- package/lib/utils/claude-client.js +373 -0
- package/lib/utils/file-operations.js +409 -0
- package/lib/utils/git-operations.js +341 -0
- package/lib/utils/logger.js +141 -0
- package/lib/utils/prompt-builder.js +283 -0
- package/lib/utils/resolution-prompt.js +291 -0
- package/package.json +52 -40
- package/templates/pre-commit +58 -411
- package/templates/prepare-commit-msg +62 -118
package/templates/pre-commit
CHANGED
|
@@ -1,432 +1,79 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
# Git Pre-commit Hook
|
|
4
|
-
#
|
|
3
|
+
# Git Pre-commit Hook - Node.js Wrapper
|
|
4
|
+
# This is a minimal wrapper that calls the actual Node.js implementation
|
|
5
|
+
# Why: Allows the Node.js script to use relative imports from its installed location
|
|
5
6
|
|
|
6
7
|
set -e
|
|
7
8
|
|
|
8
|
-
# Configuration
|
|
9
|
-
CLAUDE_CLI="claude"
|
|
10
|
-
TEMP_DIR="/tmp/code-review-$$"
|
|
11
|
-
MAX_FILE_SIZE=100000 # 100KB maximum per file
|
|
12
|
-
|
|
13
9
|
# Colors for output
|
|
14
10
|
RED='\033[0;31m'
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
NC='\033[0m'
|
|
12
|
+
|
|
13
|
+
# Convert Windows path to Git Bash/WSL path
|
|
14
|
+
# Why: npm prefix -g in Git Bash returns Windows paths (C:\...) that need conversion
|
|
15
|
+
convert_windows_path() {
|
|
16
|
+
local path="$1"
|
|
17
|
+
|
|
18
|
+
# Check if it's a Windows path (contains :\ or starts with C:)
|
|
19
|
+
if [[ "$path" =~ ^[A-Za-z]:\\ ]] || [[ "$path" =~ ^[A-Za-z]: ]]; then
|
|
20
|
+
# Convert C:\path\to\file to /c/path/to/file
|
|
21
|
+
# First, extract drive letter
|
|
22
|
+
local drive=$(echo "$path" | sed 's/^\([A-Za-z]\):.*/\1/' | tr '[:upper:]' '[:lower:]')
|
|
23
|
+
# Remove drive letter and colon, replace backslashes with forward slashes
|
|
24
|
+
local rest=$(echo "$path" | sed 's/^[A-Za-z]://' | sed 's/\\/\//g')
|
|
25
|
+
echo "/$drive$rest"
|
|
26
|
+
else
|
|
27
|
+
echo "$path"
|
|
28
|
+
fi
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
#
|
|
37
|
-
SCRIPT_PATHS=(
|
|
38
|
-
"$(dirname "$0")/check-version.sh"
|
|
39
|
-
"/usr/local/lib/node_modules/claude-git-hooks/templates/check-version.sh"
|
|
40
|
-
"/usr/lib/node_modules/claude-git-hooks/templates/check-version.sh"
|
|
41
|
-
"$HOME/.npm-global/lib/node_modules/claude-git-hooks/templates/check-version.sh"
|
|
42
|
-
"$(npm prefix -g 2>/dev/null)/lib/node_modules/claude-git-hooks/templates/check-version.sh"
|
|
43
|
-
)
|
|
31
|
+
# Function to find the Node.js script
|
|
32
|
+
find_hook_script() {
|
|
33
|
+
# Why: Try multiple locations to find where npm installed the package
|
|
34
|
+
# Checks: global npm, local node_modules, npm prefix
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
# Get npm global prefix and convert if it's a Windows path
|
|
37
|
+
local npm_prefix=$(npm prefix -g 2>/dev/null || echo "")
|
|
38
|
+
if [ -n "$npm_prefix" ]; then
|
|
39
|
+
npm_prefix=$(convert_windows_path "$npm_prefix")
|
|
49
40
|
fi
|
|
50
|
-
done
|
|
51
|
-
|
|
52
|
-
# If we find the script, execute it
|
|
53
|
-
if [ -n "$CHECK_VERSION_SCRIPT" ]; then
|
|
54
|
-
# Source the script to have access to the function
|
|
55
|
-
source "$CHECK_VERSION_SCRIPT"
|
|
56
|
-
check_version
|
|
57
|
-
fi
|
|
58
41
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# Use a temporary file to accumulate the issues
|
|
80
|
-
local TEMP_ISSUES=$(mktemp)
|
|
81
|
-
|
|
82
|
-
# Parse each blocking issue as JSON object
|
|
83
|
-
echo "$JSON_RESPONSE" | jq -c '.blockingIssues[]?' 2>/dev/null | while IFS= read -r issue; do
|
|
84
|
-
if [ -n "$issue" ]; then
|
|
85
|
-
local desc=$(echo "$issue" | jq -r '.description')
|
|
86
|
-
local file=$(echo "$issue" | jq -r '.file')
|
|
87
|
-
local line=$(echo "$issue" | jq -r '.line')
|
|
88
|
-
local method=$(echo "$issue" | jq -r '.method')
|
|
89
|
-
local severity=$(echo "$issue" | jq -r '.severity')
|
|
90
|
-
|
|
91
|
-
echo "### Issue #${issue_num} [${severity^^}]" >> "$TEMP_ISSUES"
|
|
92
|
-
echo "**Description:** ${desc}" >> "$TEMP_ISSUES"
|
|
93
|
-
echo "**Location:** ${file}:${line}" >> "$TEMP_ISSUES"
|
|
94
|
-
echo "**Method/Class:** ${method}" >> "$TEMP_ISSUES"
|
|
95
|
-
echo "" >> "$TEMP_ISSUES"
|
|
96
|
-
issue_num=$((issue_num + 1))
|
|
42
|
+
local SCRIPT_PATHS=(
|
|
43
|
+
# Global npm installation (Linux/Mac)
|
|
44
|
+
"/usr/local/lib/node_modules/claude-git-hooks/lib/hooks/pre-commit.js"
|
|
45
|
+
"/usr/lib/node_modules/claude-git-hooks/lib/hooks/pre-commit.js"
|
|
46
|
+
# Global npm installation (Windows) - node_modules directly under prefix
|
|
47
|
+
"${npm_prefix}/node_modules/claude-git-hooks/lib/hooks/pre-commit.js"
|
|
48
|
+
# Global npm installation (Unix/WSL) - lib/node_modules under prefix
|
|
49
|
+
"${npm_prefix}/lib/node_modules/claude-git-hooks/lib/hooks/pre-commit.js"
|
|
50
|
+
# Home directory npm global
|
|
51
|
+
"$HOME/.npm-global/lib/node_modules/claude-git-hooks/lib/hooks/pre-commit.js"
|
|
52
|
+
# Local node_modules (if linked or installed locally)
|
|
53
|
+
"./node_modules/claude-git-hooks/lib/hooks/pre-commit.js"
|
|
54
|
+
"../node_modules/claude-git-hooks/lib/hooks/pre-commit.js"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
for path in "${SCRIPT_PATHS[@]}"; do
|
|
58
|
+
if [ -f "$path" ]; then
|
|
59
|
+
echo "$path"
|
|
60
|
+
return 0
|
|
97
61
|
fi
|
|
98
62
|
done
|
|
99
|
-
|
|
100
|
-
# Read the accumulated content
|
|
101
|
-
ISSUES_FORMATTED=$(cat "$TEMP_ISSUES")
|
|
102
|
-
rm -f "$TEMP_ISSUES"
|
|
103
|
-
|
|
104
|
-
# Generate the prompt from the template
|
|
105
|
-
cp "$RESOLUTION_TEMPLATE" "$RESOLUTION_FILE"
|
|
106
|
-
|
|
107
|
-
# Replace placeholders - use double quotes and escape special characters
|
|
108
|
-
sed -i "s|{{REPO_NAME}}|${REPO_NAME}|g" "$RESOLUTION_FILE"
|
|
109
|
-
sed -i "s|{{BRANCH_NAME}}|${BRANCH_NAME}|g" "$RESOLUTION_FILE"
|
|
110
|
-
sed -i "s|{{COMMIT_SHA}}|${COMMIT_SHA}|g" "$RESOLUTION_FILE"
|
|
111
|
-
sed -i "s|{{FILE_COUNT}}|${FILE_COUNT}|g" "$RESOLUTION_FILE"
|
|
112
|
-
|
|
113
|
-
# Create temporary file for formatted issues
|
|
114
|
-
local TEMP_ISSUES_FILE=$(mktemp)
|
|
115
|
-
echo "$ISSUES_FORMATTED" > "$TEMP_ISSUES_FILE"
|
|
116
|
-
|
|
117
|
-
# Replace {{BLOCKING_ISSUES}} with content
|
|
118
|
-
if [ -n "$ISSUES_FORMATTED" ]; then
|
|
119
|
-
sed -i "/{{BLOCKING_ISSUES}}/r $TEMP_ISSUES_FILE" "$RESOLUTION_FILE"
|
|
120
|
-
fi
|
|
121
|
-
sed -i "s|{{BLOCKING_ISSUES}}||g" "$RESOLUTION_FILE"
|
|
122
|
-
rm -f "$TEMP_ISSUES_FILE"
|
|
123
|
-
|
|
124
|
-
# Add content from affected files
|
|
125
|
-
local TEMP_FILES=$(mktemp)
|
|
126
|
-
echo "$JSON_RESPONSE" | jq -r '.blockingIssues[].file' 2>/dev/null | sort -u | while IFS= read -r file; do
|
|
127
|
-
if [ -f "$file" ]; then
|
|
128
|
-
echo "### File: $file" >> "$TEMP_FILES"
|
|
129
|
-
echo "" >> "$TEMP_FILES"
|
|
130
|
-
echo '```' >> "$TEMP_FILES"
|
|
131
|
-
cat "$file" >> "$TEMP_FILES"
|
|
132
|
-
echo '```' >> "$TEMP_FILES"
|
|
133
|
-
echo "" >> "$TEMP_FILES"
|
|
134
|
-
fi
|
|
135
|
-
done
|
|
136
|
-
|
|
137
|
-
# Replace {{FILE_CONTENTS}} with content
|
|
138
|
-
if [ -s "$TEMP_FILES" ]; then
|
|
139
|
-
sed -i "/{{FILE_CONTENTS}}/r $TEMP_FILES" "$RESOLUTION_FILE"
|
|
140
|
-
fi
|
|
141
|
-
sed -i "s|{{FILE_CONTENTS}}||g" "$RESOLUTION_FILE"
|
|
142
|
-
rm -f "$TEMP_FILES"
|
|
143
|
-
|
|
144
|
-
echo
|
|
145
|
-
echo -e "${YELLOW}=== AI RESOLUTION PROMPT GENERATED ===${NC}"
|
|
146
|
-
echo -e "${GREEN}An AI-friendly prompt has been generated at: ${BLUE}$RESOLUTION_FILE${NC}"
|
|
147
|
-
echo -e "${YELLOW}Copy this file to a new Claude instance to resolve problems automatically.${NC}"
|
|
148
|
-
echo
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
# Configure files for SonarQube mode
|
|
152
|
-
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT_SONAR.md"
|
|
153
|
-
PROMPT_TEMPLATE=".claude/CLAUDE_ANALYSIS_PROMPT_SONAR.md"
|
|
154
63
|
|
|
155
|
-
|
|
156
|
-
# Check that the prompt template exists
|
|
157
|
-
if [ ! -f "$PROMPT_TEMPLATE" ]; then
|
|
158
|
-
error "Prompt template not found: $PROMPT_TEMPLATE"
|
|
159
|
-
error "Claude configuration files appear to be incomplete."
|
|
160
|
-
error "Please reinstall claude-git-hooks by running:"
|
|
161
|
-
error " claude-hooks install --force"
|
|
162
|
-
exit 1
|
|
163
|
-
fi
|
|
164
|
-
# Function to clean temporary files
|
|
165
|
-
cleanup() {
|
|
166
|
-
rm -rf "$TEMP_DIR"
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
# Configure cleanup on exit
|
|
170
|
-
trap cleanup EXIT
|
|
171
|
-
|
|
172
|
-
# Create temporary directory
|
|
173
|
-
mkdir -p "$TEMP_DIR"
|
|
174
|
-
|
|
175
|
-
# Check if Claude CLI is installed (only for code analysis)
|
|
176
|
-
if ! command -v "$CLAUDE_CLI" &> /dev/null; then
|
|
177
|
-
error "Claude CLI is not installed or not found in PATH"
|
|
178
|
-
error "Install Claude CLI from: https://github.com/anthropics/claude-cli"
|
|
179
|
-
exit 1
|
|
180
|
-
fi
|
|
181
|
-
|
|
182
|
-
# Now check if there are Java files to analyze
|
|
183
|
-
JAVA_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(java|xml|properties|yml|yaml)$' || true)
|
|
184
|
-
|
|
185
|
-
if [ -z "$JAVA_FILES" ]; then
|
|
186
|
-
log "No Java/configuration files to review"
|
|
187
|
-
exit 0
|
|
188
|
-
fi
|
|
189
|
-
|
|
190
|
-
# Check if the guidelines file exists
|
|
191
|
-
if [ ! -f "$GUIDELINES_FILE" ]; then
|
|
192
|
-
error "Guidelines file not found: $GUIDELINES_FILE"
|
|
193
|
-
error "Please reinstall claude-git-hooks by running:"
|
|
194
|
-
error " claude-hooks install --force"
|
|
195
|
-
exit 1
|
|
196
|
-
fi
|
|
197
|
-
|
|
198
|
-
log "Java/config files to review: $(echo "$JAVA_FILES" | wc -l)"
|
|
199
|
-
|
|
200
|
-
# Function to filter content with SKIP-ANALYSIS
|
|
201
|
-
filter_skip_analysis() {
|
|
202
|
-
local file_content="$1"
|
|
203
|
-
local filtered_content=""
|
|
204
|
-
local skip_next_line=false
|
|
205
|
-
local inside_skip_block=false
|
|
206
|
-
|
|
207
|
-
while IFS= read -r line; do
|
|
208
|
-
# Detect SKIP-ANALYSIS for single line
|
|
209
|
-
if echo "$line" | grep -q "// SKIP-ANALYSIS"; then
|
|
210
|
-
skip_next_line=true
|
|
211
|
-
continue
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
# Detect start/end of SKIP_ANALYSIS_BLOCK
|
|
215
|
-
if echo "$line" | grep -q "// SKIP_ANALYSIS_BLOCK"; then
|
|
216
|
-
if [ "$inside_skip_block" = true ]; then
|
|
217
|
-
# End of block
|
|
218
|
-
inside_skip_block=false
|
|
219
|
-
else
|
|
220
|
-
# Start of block
|
|
221
|
-
inside_skip_block=true
|
|
222
|
-
fi
|
|
223
|
-
continue
|
|
224
|
-
fi
|
|
225
|
-
|
|
226
|
-
# If we're inside a block, skip the line
|
|
227
|
-
if [ "$inside_skip_block" = true ]; then
|
|
228
|
-
continue
|
|
229
|
-
fi
|
|
230
|
-
|
|
231
|
-
# If we should skip the next line (single comment)
|
|
232
|
-
if [ "$skip_next_line" = true ]; then
|
|
233
|
-
skip_next_line=false
|
|
234
|
-
continue
|
|
235
|
-
fi
|
|
236
|
-
|
|
237
|
-
# Add line to filtered content
|
|
238
|
-
filtered_content="${filtered_content}${line}"$'\n'
|
|
239
|
-
done <<< "$file_content"
|
|
240
|
-
|
|
241
|
-
echo "$filtered_content"
|
|
64
|
+
return 1
|
|
242
65
|
}
|
|
243
66
|
|
|
244
|
-
#
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
# Copy the prompt template
|
|
248
|
-
cat "$PROMPT_TEMPLATE" > "$PROMPT_FILE"
|
|
249
|
-
|
|
250
|
-
# Add the guidelines
|
|
251
|
-
echo "=== EVALUATION GUIDELINES ===" >> "$PROMPT_FILE"
|
|
252
|
-
cat "$GUIDELINES_FILE" >> "$PROMPT_FILE"
|
|
253
|
-
echo -e "\n\n=== CHANGES TO REVIEW ===\n" >> "$PROMPT_FILE"
|
|
254
|
-
|
|
255
|
-
# Process each Java file
|
|
256
|
-
FILE_COUNT=0
|
|
257
|
-
for FILE in $JAVA_FILES; do
|
|
258
|
-
if [ -f "$FILE" ]; then
|
|
259
|
-
FILE_SIZE=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE" 2>/dev/null || echo "0")
|
|
260
|
-
|
|
261
|
-
if [ "$FILE_SIZE" -gt "$MAX_FILE_SIZE" ]; then
|
|
262
|
-
warning "File $FILE too large ($FILE_SIZE bytes), skipping..."
|
|
263
|
-
continue
|
|
264
|
-
fi
|
|
265
|
-
|
|
266
|
-
echo -e "\n--- Archivo: $FILE ---" >> "$PROMPT_FILE"
|
|
267
|
-
|
|
268
|
-
# Get the diff and filter it
|
|
269
|
-
DIFF_CONTENT=$(git diff --cached "$FILE" 2>/dev/null || echo "Could not get diff")
|
|
270
|
-
FILTERED_DIFF=$(filter_skip_analysis "$DIFF_CONTENT")
|
|
271
|
-
|
|
272
|
-
# Show the filtered diff of the file
|
|
273
|
-
echo -e "\nDiff:" >> "$PROMPT_FILE"
|
|
274
|
-
echo "$FILTERED_DIFF" >> "$PROMPT_FILE"
|
|
275
|
-
|
|
276
|
-
# If it's a new file, show complete filtered content
|
|
277
|
-
if git diff --cached --name-status | grep "^A.*$FILE" > /dev/null; then
|
|
278
|
-
echo -e "\nComplete content (new file):" >> "$PROMPT_FILE"
|
|
279
|
-
FILE_CONTENT=$(git show ":$FILE" 2>/dev/null || cat "$FILE")
|
|
280
|
-
FILTERED_CONTENT=$(filter_skip_analysis "$FILE_CONTENT")
|
|
281
|
-
echo "$FILTERED_CONTENT" >> "$PROMPT_FILE"
|
|
282
|
-
fi
|
|
283
|
-
|
|
284
|
-
FILE_COUNT=$((FILE_COUNT + 1))
|
|
285
|
-
fi
|
|
286
|
-
done
|
|
287
|
-
|
|
288
|
-
if [ "$FILE_COUNT" -eq 0 ]; then
|
|
289
|
-
log "No valid files found to review"
|
|
290
|
-
exit 0
|
|
291
|
-
fi
|
|
292
|
-
|
|
293
|
-
if [ "$FILE_COUNT" -gt 10 ]; then
|
|
294
|
-
warning "Too many files to review ($FILE_COUNT)"
|
|
295
|
-
warning "Consider splitting the commit into smaller parts"
|
|
296
|
-
exit 0
|
|
297
|
-
fi
|
|
298
|
-
|
|
299
|
-
log "Sending $FILE_COUNT files for review with Claude..."
|
|
300
|
-
|
|
301
|
-
# Send to Claude and capture response
|
|
302
|
-
RESPONSE_FILE="$TEMP_DIR/code_review_response.txt"
|
|
303
|
-
|
|
304
|
-
# Execute Claude CLI and capture the response
|
|
305
|
-
if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
|
|
306
|
-
# Extract the JSON from the response
|
|
307
|
-
JSON_RESPONSE=$(sed -n '/^{/,/^}/p' "$RESPONSE_FILE" | head -n 1000)
|
|
308
|
-
|
|
309
|
-
if [ -z "$JSON_RESPONSE" ]; then
|
|
310
|
-
error "Did not receive a valid JSON response from Claude"
|
|
311
|
-
error "Complete response:"
|
|
312
|
-
cat "$RESPONSE_FILE"
|
|
313
|
-
exit 1
|
|
314
|
-
fi
|
|
315
|
-
|
|
316
|
-
# Save JSON for debug if activated
|
|
317
|
-
if [ -n "$DEBUG" ]; then
|
|
318
|
-
echo "$JSON_RESPONSE" > ./debug-claude-response.json
|
|
319
|
-
log "Response saved to debug-claude-response.json"
|
|
320
|
-
fi
|
|
321
|
-
|
|
322
|
-
# Parse the response using jq
|
|
323
|
-
APPROVED=$(echo "$JSON_RESPONSE" | jq -r '.approved // false')
|
|
324
|
-
SCORE=$(echo "$JSON_RESPONSE" | jq -r '.score // 0')
|
|
325
|
-
RECOMMENDATIONS=$(echo "$JSON_RESPONSE" | jq -r '.recommendations[]?' 2>/dev/null | sed '/^$/d')
|
|
326
|
-
|
|
327
|
-
# Parse blockingIssues as objects and extract descriptions
|
|
328
|
-
BLOCKING_ISSUES=""
|
|
329
|
-
BLOCKING_COUNT=$(echo "$JSON_RESPONSE" | jq '.blockingIssues | length' 2>/dev/null || echo "0")
|
|
330
|
-
|
|
331
|
-
if [ "$BLOCKING_COUNT" -gt 0 ]; then
|
|
332
|
-
BLOCKING_ISSUES=$(echo "$JSON_RESPONSE" | jq -r '.blockingIssues[].description' 2>/dev/null | sed '/^$/d')
|
|
333
|
-
fi
|
|
334
|
-
|
|
335
|
-
# Always use SonarQube mode (as per v1.4.1)
|
|
336
|
-
QUALITY_GATE=$(echo "$JSON_RESPONSE" | jq -r '.QUALITY_GATE // ""' 2>/dev/null)
|
|
337
|
-
|
|
338
|
-
# Show SonarQube style results
|
|
339
|
-
echo
|
|
340
|
-
echo "╔════════════════════════════════════════════════════════════════════╗"
|
|
341
|
-
echo "║ CODE QUALITY ANALYSIS ║"
|
|
342
|
-
echo "╚════════════════════════════════════════════════════════════════════╝"
|
|
343
|
-
echo
|
|
344
|
-
|
|
345
|
-
# Quality Gate Status
|
|
346
|
-
if [ "$QUALITY_GATE" = "PASSED" ]; then
|
|
347
|
-
echo -e "${GREEN}✓ Quality Gate: PASSED${NC}"
|
|
348
|
-
else
|
|
349
|
-
echo -e "${RED}✗ Quality Gate: FAILED${NC}"
|
|
350
|
-
fi
|
|
351
|
-
echo
|
|
352
|
-
|
|
353
|
-
# Metrics
|
|
354
|
-
METRICS=$(echo "$JSON_RESPONSE" | jq -r '.metrics // {}' 2>/dev/null)
|
|
355
|
-
if [ "$METRICS" != "{}" ] && [ "$METRICS" != "null" ]; then
|
|
356
|
-
echo "📊 METRICS"
|
|
357
|
-
echo "├─ Reliability: $(echo "$METRICS" | jq -r '.reliability // "?"' 2>/dev/null)"
|
|
358
|
-
echo "├─ Security: $(echo "$METRICS" | jq -r '.security // "?"' 2>/dev/null)"
|
|
359
|
-
echo "├─ Maintainability: $(echo "$METRICS" | jq -r '.maintainability // "?"' 2>/dev/null)"
|
|
360
|
-
echo "├─ Coverage: $(echo "$METRICS" | jq -r '.coverage // "?"' 2>/dev/null)%"
|
|
361
|
-
echo "├─ Duplications: $(echo "$METRICS" | jq -r '.duplications // "?"' 2>/dev/null)%"
|
|
362
|
-
echo "└─ Complexity: $(echo "$METRICS" | jq -r '.complexity // "?"' 2>/dev/null)"
|
|
363
|
-
echo
|
|
364
|
-
fi
|
|
365
|
-
|
|
366
|
-
# Issues Summary
|
|
367
|
-
ISSUES=$(echo "$JSON_RESPONSE" | jq -r '.issues // {}' 2>/dev/null)
|
|
368
|
-
if [ "$ISSUES" != "{}" ] && [ "$ISSUES" != "null" ]; then
|
|
369
|
-
echo "📋 ISSUES SUMMARY"
|
|
370
|
-
BLOCKER=$(echo "$ISSUES" | jq -r '.blocker // 0' 2>/dev/null)
|
|
371
|
-
CRITICAL=$(echo "$ISSUES" | jq -r '.critical // 0' 2>/dev/null)
|
|
372
|
-
MAJOR=$(echo "$ISSUES" | jq -r '.major // 0' 2>/dev/null)
|
|
373
|
-
MINOR=$(echo "$ISSUES" | jq -r '.minor // 0' 2>/dev/null)
|
|
374
|
-
INFO=$(echo "$ISSUES" | jq -r '.info // 0' 2>/dev/null)
|
|
375
|
-
TOTAL=$((BLOCKER + CRITICAL + MAJOR + MINOR + INFO))
|
|
376
|
-
|
|
377
|
-
echo "Total: $TOTAL issues found"
|
|
378
|
-
[ "$BLOCKER" -gt 0 ] && echo -e " ${RED}🔴 Blocker: $BLOCKER${NC}"
|
|
379
|
-
[ "$CRITICAL" -gt 0 ] && echo -e " ${RED}🟠 Critical: $CRITICAL${NC}"
|
|
380
|
-
[ "$MAJOR" -gt 0 ] && echo -e " ${YELLOW}🟡 Major: $MAJOR${NC}"
|
|
381
|
-
[ "$MINOR" -gt 0 ] && echo " 🔵 Minor: $MINOR"
|
|
382
|
-
[ "$INFO" -gt 0 ] && echo " ⚪ Info: $INFO"
|
|
383
|
-
echo
|
|
384
|
-
fi
|
|
385
|
-
|
|
386
|
-
# Detailed Issues
|
|
387
|
-
DETAILS_COUNT=$(echo "$JSON_RESPONSE" | jq -r '.details | length' 2>/dev/null)
|
|
388
|
-
if [ "$DETAILS_COUNT" -gt 0 ] 2>/dev/null; then
|
|
389
|
-
echo "🔍 DETAILED ISSUES"
|
|
390
|
-
echo "$JSON_RESPONSE" | jq -r '.details[]? |
|
|
391
|
-
"[\(.severity)] \(.type) in \(.file):\(.line // "?")\n \(.message)\n"' 2>/dev/null
|
|
392
|
-
fi
|
|
393
|
-
|
|
394
|
-
# Security Hotspots
|
|
395
|
-
HOTSPOTS=$(echo "$JSON_RESPONSE" | jq -r '.securityHotspots // 0' 2>/dev/null)
|
|
396
|
-
if [ "$HOTSPOTS" -gt 0 ] 2>/dev/null; then
|
|
397
|
-
echo "🔥 SECURITY HOTSPOTS: $HOTSPOTS found"
|
|
398
|
-
echo " Review security-sensitive code carefully"
|
|
399
|
-
echo
|
|
400
|
-
fi
|
|
401
|
-
|
|
402
|
-
# Check if commit should be blocked
|
|
403
|
-
if [ "$QUALITY_GATE" = "FAILED" ] || [ "$APPROVED" = "false" ]; then
|
|
404
|
-
echo
|
|
405
|
-
error "❌ Commit blocked due to quality gate failure"
|
|
406
|
-
|
|
407
|
-
# Show blocking issues if they exist
|
|
408
|
-
if [ -n "$BLOCKING_ISSUES" ] && [ "$BLOCKING_ISSUES" != "null" ]; then
|
|
409
|
-
echo
|
|
410
|
-
echo "=== CRITICAL ISSUES ==="
|
|
411
|
-
echo "$BLOCKING_ISSUES" | sed 's/^/- /'
|
|
412
|
-
fi
|
|
413
|
-
|
|
414
|
-
# Generate AI-friendly resolution prompt if there are blocking issues
|
|
415
|
-
if [ "$BLOCKING_COUNT" -gt 0 ]; then
|
|
416
|
-
generate_resolution_prompt
|
|
417
|
-
fi
|
|
418
|
-
|
|
419
|
-
exit 1
|
|
420
|
-
fi
|
|
67
|
+
# Find the Node.js script
|
|
68
|
+
NODE_HOOK=$(find_hook_script)
|
|
421
69
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
error "Error executing Claude CLI"
|
|
427
|
-
error "Check that Claude CLI is configured correctly"
|
|
428
|
-
cat "$RESPONSE_FILE"
|
|
70
|
+
if [ -z "$NODE_HOOK" ]; then
|
|
71
|
+
echo -e "${RED}Error: Could not find pre-commit.js${NC}" >&2
|
|
72
|
+
echo "Claude Git Hooks may not be properly installed." >&2
|
|
73
|
+
echo "Try running: claude-hooks install --force" >&2
|
|
429
74
|
exit 1
|
|
430
75
|
fi
|
|
431
76
|
|
|
432
|
-
|
|
77
|
+
# Execute the Node.js script
|
|
78
|
+
# Why: Pass all arguments ($@) to support future hook extensions
|
|
79
|
+
exec node "$NODE_HOOK" "$@"
|