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