@@ -81,7 +81,7 @@ source "$REPO_ROOT/lib/pattern-loader.sh"
8181# This is the ONLY place the version number should be defined.
8282# All other references (logs, JSON, banners) use this variable.
8383# Update this ONE line when bumping versions - never hardcode elsewhere.
84- SCRIPT_VERSION=" 2.2.4 "
84+ SCRIPT_VERSION=" 2.2.5 "
8585
8686# Get the start/end line range for the enclosing function/method.
8787#
@@ -147,6 +147,7 @@ EXCLUDE_DIRS="vendor node_modules .git tests .next dist build"
147147EXCLUDE_FILES=" *.min.js *bundle*.js *.min.css"
148148DEFAULT_FIXTURE_VALIDATION_COUNT=20 # Number of fixtures to validate by default (can be overridden)
149149SKIP_CLONE_DETECTION=false # Clone detection runs by default (use --skip-clone-detection to disable)
150+ SKIP_MAGIC_STRINGS=false # Magic String Detector runs by default (use --skip-magic-strings to disable)
150151
151152# ============================================================
152153# AI TRIAGE CONFIGURATION (Phase 1: Claude Code Integration)
@@ -467,6 +468,7 @@ OPTIONS:
467468 --baseline <path> Use custom baseline file path (default: .hcc-baseline)
468469 --ignore-baseline Ignore baseline file even if present
469470 --enable-clone-detection Enable function clone detection (disabled by default for performance)
471+ --skip-magic-strings Skip Magic String Detector (last resort for timeout issues)
470472
471473AI TRIAGE OPTIONS:
472474
@@ -829,6 +831,10 @@ while [[ $# -gt 0 ]]; do
829831 SKIP_CLONE_DETECTION=true
830832 shift
831833 ;;
834+ --skip-magic-strings)
835+ SKIP_MAGIC_STRINGS=true
836+ shift
837+ ;;
832838 --no-context)
833839 CONTEXT_LINES=0
834840 shift
@@ -888,13 +894,67 @@ debug_echo "Arguments parsed. PATHS=$PATHS"
888894debug_echo " OUTPUT_FORMAT=$OUTPUT_FORMAT "
889895debug_echo " ENABLE_LOGGING=$ENABLE_LOGGING "
890896
897+ # ============================================================
898+ # LOAD .wpcignore FILE (if present)
899+ # ============================================================
900+ # Load exclusions from .wpcignore file (like .gitignore)
901+ # Looks for .wpcignore in:
902+ # 1. Scan path directory (if scanning a directory)
903+ # 2. Current working directory
904+ # 3. Repository root
905+ load_wpcignore () {
906+ local wpcignore_file=" "
907+
908+ # Check scan path first (if it's a directory)
909+ if [ -d " $PATHS " ] && [ -f " $PATHS /.wpcignore" ]; then
910+ wpcignore_file=" $PATHS /.wpcignore"
911+ # Check current directory
912+ elif [ -f " .wpcignore" ]; then
913+ wpcignore_file=" .wpcignore"
914+ # Check repository root
915+ elif [ -f " $REPO_ROOT /.wpcignore" ]; then
916+ wpcignore_file=" $REPO_ROOT /.wpcignore"
917+ fi
918+
919+ if [ -n " $wpcignore_file " ]; then
920+ debug_echo " Loading exclusions from: $wpcignore_file "
921+
922+ # Read .wpcignore line by line, skip comments and empty lines
923+ while IFS= read -r line || [ -n " $line " ]; do
924+ # Skip comments and empty lines
925+ if [ -z " $line " ] || echo " $line " | grep -q ' ^[[:space:]]*#' ; then
926+ continue
927+ fi
928+
929+ # Trim whitespace
930+ line=$( echo " $line " | sed ' s/^[[:space:]]*//;s/[[:space:]]*$//' )
931+
932+ # Add to appropriate exclusion list
933+ if echo " $line " | grep -q ' \*' ; then
934+ # File pattern (contains wildcard)
935+ EXCLUDE_FILES=" $EXCLUDE_FILES $line "
936+ else
937+ # Directory pattern (no wildcard)
938+ # Remove trailing slash if present
939+ line=$( echo " $line " | sed ' s|/$||' )
940+ EXCLUDE_DIRS=" $EXCLUDE_DIRS $line "
941+ fi
942+ done < " $wpcignore_file "
943+
944+ debug_echo " Loaded exclusions from .wpcignore"
945+ fi
946+ }
947+
948+ # Load .wpcignore if present
949+ load_wpcignore
950+
891951# If scanning a tests directory, remove 'tests' from exclusions
892952# Use portable method (no \b word boundary which is GNU-specific)
893953if echo " $PATHS " | grep -q " tests" ; then
894954 EXCLUDE_DIRS=" vendor node_modules .git .next dist build"
895955fi
896956
897- # Build exclude arguments
957+ # Build exclude arguments for grep
898958EXCLUDE_ARGS=" "
899959for dir in $EXCLUDE_DIRS ; do
900960 EXCLUDE_ARGS=" $EXCLUDE_ARGS --exclude-dir=$dir "
@@ -903,6 +963,22 @@ for file in $EXCLUDE_FILES; do
903963 EXCLUDE_ARGS=" $EXCLUDE_ARGS --exclude=$file "
904964done
905965
966+ # Build grep -v exclusions for find commands (used in clone detection and file caching)
967+ # This converts EXCLUDE_DIRS into a series of grep -v '/dirname/' commands
968+ build_grep_exclusions () {
969+ local grep_cmd=" "
970+ for dir in $EXCLUDE_DIRS ; do
971+ if [ -n " $grep_cmd " ]; then
972+ grep_cmd=" $grep_cmd | grep -v '/$dir /'"
973+ else
974+ grep_cmd=" grep -v '/$dir /'"
975+ fi
976+ done
977+ echo " $grep_cmd "
978+ }
979+
980+ GREP_EXCLUSIONS=$( build_grep_exclusions)
981+
906982# ============================================================================
907983# Helper Functions (must be defined before logging setup)
908984# ============================================================================
@@ -2579,6 +2655,8 @@ process_aggregated_pattern() {
25792655 # Extract captured groups and aggregate
25802656 if [ -n " $matches " ]; then
25812657 local iteration=0
2658+ local last_progress_time=$( date +%s 2> /dev/null || echo " 0" )
2659+
25822660 while IFS= read -r match; do
25832661 [ -z " $match " ] && continue
25842662
@@ -2589,6 +2667,17 @@ process_aggregated_pattern() {
25892667 break
25902668 fi
25912669
2670+ # PROGRESS: Show progress every 10 seconds during string extraction
2671+ local current_time=$( date +%s 2> /dev/null || echo " 0" )
2672+ if [ " $current_time " != " 0" ] && [ " $last_progress_time " != " 0" ]; then
2673+ local time_diff=$(( current_time - last_progress_time))
2674+ if [ " $time_diff " -ge 10 ]; then
2675+ section_progress
2676+ text_echo " ${BLUE} Processing match $iteration of $match_count ...${NC} "
2677+ last_progress_time=$current_time
2678+ fi
2679+ fi
2680+
25922681 local file=$( echo " $match " | cut -d: -f1)
25932682 local line=$( echo " $match " | cut -d: -f2)
25942683 local code=$( echo " $match " | cut -d: -f3-)
@@ -2618,8 +2707,11 @@ process_aggregated_pattern() {
26182707 # Aggregate by captured string
26192708 if [ -f " $temp_matches " ] && [ -s " $temp_matches " ]; then
26202709 local unique_strings=$( cut -d' |' -f1 " $temp_matches " | sort -u)
2710+ local total_unique_strings=$( echo " $unique_strings " | wc -l | tr -d ' ' )
26212711
26222712 local string_iteration=0
2713+ local last_string_progress_time=$( date +%s 2> /dev/null || echo " 0" )
2714+
26232715 while IFS= read -r string; do
26242716 [ -z " $string " ] && continue
26252717
@@ -2630,6 +2722,17 @@ process_aggregated_pattern() {
26302722 break
26312723 fi
26322724
2725+ # PROGRESS: Show progress every 10 seconds during string aggregation
2726+ local current_time=$( date +%s 2> /dev/null || echo " 0" )
2727+ if [ " $current_time " != " 0" ] && [ " $last_string_progress_time " != " 0" ]; then
2728+ local time_diff=$(( current_time - last_string_progress_time))
2729+ if [ " $time_diff " -ge 10 ]; then
2730+ section_progress
2731+ text_echo " ${BLUE} Analyzing string $string_iteration of $total_unique_strings ...${NC} "
2732+ last_string_progress_time=$current_time
2733+ fi
2734+ fi
2735+
26332736 # Unescape the string for comparison
26342737 local unescaped_string=$( echo " $string " | sed ' s/\\|/|/g' )
26352738
@@ -2719,7 +2822,13 @@ process_clone_detection() {
27192822 # Directory provided - find all PHP files
27202823 # PERFORMANCE: Wrap find in timeout to prevent hangs
27212824 local find_exit_code=0
2722- php_files=$( run_with_timeout " $MAX_SCAN_TIME " find " $PATHS " -name " *.php" -type f 2> /dev/null | grep -v ' /vendor/' | grep -v ' /node_modules/' ) || find_exit_code=$?
2825+
2826+ # Apply exclusions from EXCLUDE_DIRS (includes .wpcignore entries)
2827+ if [ -n " $GREP_EXCLUSIONS " ]; then
2828+ php_files=$( run_with_timeout " $MAX_SCAN_TIME " sh -c " find '$PATHS ' -name '*.php' -type f 2>/dev/null | $GREP_EXCLUSIONS " ) || find_exit_code=$?
2829+ else
2830+ php_files=$( run_with_timeout " $MAX_SCAN_TIME " find " $PATHS " -name " *.php" -type f 2> /dev/null) || find_exit_code=$?
2831+ fi
27232832
27242833 # Check for timeout (exit code 124)
27252834 if [ " $find_exit_code " -eq 124 ]; then
@@ -3234,10 +3343,12 @@ else
32343343 # Create temp file for caching
32353344 PHP_FILE_LIST_CACHE=$( mktemp)
32363345
3237- # Find all PHP files (excluding vendor/node_modules)
3238- find " $PATHS " -name " *.php" -type f 2> /dev/null | \
3239- grep -v ' /vendor/' | \
3240- grep -v ' /node_modules/' > " $PHP_FILE_LIST_CACHE "
3346+ # Find all PHP files (apply exclusions from EXCLUDE_DIRS including .wpcignore)
3347+ if [ -n " $GREP_EXCLUSIONS " ]; then
3348+ sh -c " find '$PATHS ' -name '*.php' -type f 2>/dev/null | $GREP_EXCLUSIONS " > " $PHP_FILE_LIST_CACHE "
3349+ else
3350+ find " $PATHS " -name " *.php" -type f 2> /dev/null > " $PHP_FILE_LIST_CACHE "
3351+ fi
32413352
32423353 PHP_FILE_COUNT=$( wc -l < " $PHP_FILE_LIST_CACHE " | tr -d ' ' )
32433354
@@ -6091,10 +6202,18 @@ fi
60916202# Magic String Detector ("DRY") - Aggregated Patterns
60926203# ============================================================================
60936204
6094- text_echo " ${BLUE} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC} "
6095- text_echo " ${BLUE} MAGIC STRING DETECTOR (\" DRY\" )${NC} "
6096- text_echo " ${BLUE} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC} "
6097- text_echo " "
6205+ # Check if Magic String Detector should be skipped
6206+ if [ " $SKIP_MAGIC_STRINGS " = " true" ]; then
6207+ text_echo " ${BLUE} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC} "
6208+ text_echo " ${BLUE} MAGIC STRING DETECTOR (\" DRY\" )${NC} "
6209+ text_echo " ${BLUE} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC} "
6210+ text_echo " ${YELLOW} ⚠ Skipped (--skip-magic-strings flag enabled)${NC} "
6211+ text_echo " "
6212+ else
6213+ text_echo " ${BLUE} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC} "
6214+ text_echo " ${BLUE} MAGIC STRING DETECTOR (\" DRY\" )${NC} "
6215+ text_echo " ${BLUE} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC} "
6216+ text_echo " "
60986217
60996218# Find all aggregated patterns
61006219AGGREGATED_PATTERNS=" "
61506269
61516270section_end
61526271profile_end " MAGIC_STRING_DETECTOR"
6272+ fi # End of SKIP_MAGIC_STRINGS check
6273+
61536274profile_start " FUNCTION_CLONE_DETECTOR"
61546275section_start " Function Clone Detector"
61556276
0 commit comments