Skip to content

Commit 544acb4

Browse files
committed
Magic String fixes
1 parent 8c9d8f2 commit 544acb4

2 files changed

Lines changed: 211 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,85 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
---
11+
12+
## [2.2.5] - 2026-02-07
13+
14+
### Added
15+
16+
#### .wpcignore Support - Exclude Directories from Scans
17+
18+
- **Added .wpcignore file support** (similar to .gitignore) to exclude directories and files from scans
19+
- Automatically loads exclusions from `.wpcignore` in scan path, current directory, or repository root
20+
- Supports directory patterns (e.g., `tools/`, `.git/`, `node_modules/`)
21+
- Supports file patterns with wildcards (e.g., `*.min.js`, `*.log`)
22+
- Comments (lines starting with `#`) and empty lines are ignored
23+
- **Impact:** Prevents scanning embedded dependencies, version control files, and build artifacts
24+
- **Use case:** Prevents recursive scanning when AI-DDTK scans itself (WPCC embedded at `tools/wp-code-check/`)
25+
26+
- **Created .wpcignore template** at `dist/templates/.wpcignore.template`
27+
- Sensible defaults: `tools/`, `.git/`, `node_modules/`, `vendor/`, `dist/logs/`, `dist/reports/`
28+
- Minified files: `*.min.js`, `*.min.css`, `*bundle*.js`
29+
- Organized by category with comments explaining each exclusion
30+
- Ready to copy to project root and customize
31+
32+
- **Created .wpcignore for AI-DDTK** at `/Users/noelsaw/Documents/GH Repos/AI-DDTK/.wpcignore`
33+
- Excludes `tools/` directory to prevent WPCC from scanning its own embedded copy
34+
- Prevents timeout/stalling issues when scanning AI-DDTK repository
35+
36+
#### Progress Indicators for Magic String Detector
37+
38+
- **Added progress indicators to Magic String Detector aggregation loops**
39+
- Shows "Processing match X of Y..." every 10 seconds during string extraction (lines 2582-2619)
40+
- Shows "Analyzing string X of Y..." every 10 seconds during string aggregation (lines 2631-2687)
41+
- Follows same pattern as clone detection progress indicators (added in v1.0.85)
42+
- **Impact:** Users can see progress during long scans, reducing perceived wait time
43+
- **Fixes:** Stalling/timeout issues reported when scanning large codebases (10K+ files)
44+
45+
#### --skip-magic-strings Flag - Last Resort for Timeout Issues
46+
47+
- **Added `--skip-magic-strings` command-line flag** to completely skip Magic String Detector phase
48+
- **Use case:** Last resort option when scans timeout during Magic String Detector aggregation
49+
- **Behavior:** Shows warning message "⚠ Skipped (--skip-magic-strings flag enabled)" and bypasses entire phase
50+
- **Pattern:** Follows same approach as existing `--skip-clone-detection` flag
51+
- **Impact:** Allows scans to complete even when Magic String Detector would timeout
52+
- **Trade-off:** Skips detection of duplicate option names, transient keys, and capability strings
53+
- **Example:** `wpcc --paths . --skip-magic-strings --format json`
54+
55+
### Changed
56+
57+
- **Improved exclusion handling in clone detection and file caching**
58+
- Clone detection now uses dynamic `GREP_EXCLUSIONS` from `EXCLUDE_DIRS` (includes .wpcignore entries)
59+
- File caching now applies .wpcignore exclusions instead of hardcoded `vendor/` and `node_modules/`
60+
- Added `build_grep_exclusions()` helper function to convert `EXCLUDE_DIRS` to grep -v commands
61+
- **Impact:** Consistent exclusion behavior across all scan phases
62+
63+
### Technical Details
64+
65+
- **Files Modified:**
66+
- `dist/bin/check-performance.sh`:
67+
- Added `SKIP_MAGIC_STRINGS=false` variable (line 150)
68+
- Added `--skip-magic-strings` to help text (line 471)
69+
- Added `--skip-magic-strings` argument parsing (lines 834-837)
70+
- Added skip logic for Magic String Detector (lines 6201-6218, closing fi at line 6272)
71+
- Added `load_wpcignore()` function (lines 900-941)
72+
- Added `build_grep_exclusions()` helper (lines 960-970)
73+
- Updated `process_aggregated_pattern()` with progress indicators (lines 2579-2687)
74+
- Updated `process_clone_detection()` to use dynamic exclusions (lines 2815-2834)
75+
- Updated file caching to use dynamic exclusions (lines 3337-3347)
76+
- `dist/templates/.wpcignore.template` - Created
77+
- `/Users/noelsaw/Documents/GH Repos/AI-DDTK/.wpcignore` - Created
78+
79+
- **Version:** Bumped to 2.2.5
80+
81+
### Documentation
82+
83+
- **TODO:** Update README.md with .wpcignore documentation
84+
- **TODO:** Update SHELL-QUICKSTART.md with .wpcignore usage
85+
- **TODO:** Update dist/TEMPLATES/_AI_INSTRUCTIONS.md with .wpcignore troubleshooting
86+
87+
---
88+
1089
### Changed
1190

1291
#### Documentation: AI-DDTK Integration

dist/bin/check-performance.sh

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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"
147147
EXCLUDE_FILES="*.min.js *bundle*.js *.min.css"
148148
DEFAULT_FIXTURE_VALIDATION_COUNT=20 # Number of fixtures to validate by default (can be overridden)
149149
SKIP_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
471473
AI 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"
888894
debug_echo "OUTPUT_FORMAT=$OUTPUT_FORMAT"
889895
debug_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)
893953
if echo "$PATHS" | grep -q "tests"; then
894954
EXCLUDE_DIRS="vendor node_modules .git .next dist build"
895955
fi
896956

897-
# Build exclude arguments
957+
# Build exclude arguments for grep
898958
EXCLUDE_ARGS=""
899959
for 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"
904964
done
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
61006219
AGGREGATED_PATTERNS=""
@@ -6150,6 +6269,8 @@ fi
61506269
61516270
section_end
61526271
profile_end "MAGIC_STRING_DETECTOR"
6272+
fi # End of SKIP_MAGIC_STRINGS check
6273+
61536274
profile_start "FUNCTION_CLONE_DETECTOR"
61546275
section_start "Function Clone Detector"
61556276

0 commit comments

Comments
 (0)