@theglitchking/gimme-the-lint 1.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/.claude-plugin/commands/lint-baseline.md +33 -0
- package/.claude-plugin/commands/lint-status.md +26 -0
- package/.claude-plugin/commands/lint.md +40 -0
- package/.claude-plugin/plugin.json +48 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +310 -0
- package/agents/linting-agent.md +24 -0
- package/bin/gimme-the-lint.js +220 -0
- package/bin/postinstall.js +29 -0
- package/githooks/install.sh +43 -0
- package/githooks/pre-commit +49 -0
- package/githooks/pre-push +39 -0
- package/install.sh +58 -0
- package/lib/config-manager.js +98 -0
- package/lib/directory-discovery.js +120 -0
- package/lib/drift-detector.js +92 -0
- package/lib/git-hooks-manager.js +115 -0
- package/lib/index.js +19 -0
- package/lib/installer.js +110 -0
- package/lib/manifest-manager.js +58 -0
- package/lib/venv-manager.js +94 -0
- package/package.json +82 -0
- package/scripts/dashboard.sh +168 -0
- package/scripts/eslint-baseline.sh +227 -0
- package/scripts/ruff-baseline.sh +226 -0
- package/scripts/run-checks.sh +291 -0
- package/scripts/setup-venv.sh +81 -0
- package/scripts/validate-version.sh +46 -0
- package/templates/.gitleaks.template.toml +71 -0
- package/templates/.pre-commit-config.template.yaml +51 -0
- package/templates/commitlint.config.template.js +24 -0
- package/templates/eslint.config.template.js +124 -0
- package/templates/pyproject.template.toml +61 -0
- package/templates/requirements.linting.txt +11 -0
- package/uninstall.sh +54 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gimme-the-lint: Progressive Linting Dashboard
|
|
3
|
+
# Purpose: Show progressive linting progress across entire codebase
|
|
4
|
+
# Usage: ./scripts/dashboard.sh
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
if [ -n "$GIMME_PROJECT_ROOT" ]; then
|
|
9
|
+
PROJECT_ROOT="$GIMME_PROJECT_ROOT"
|
|
10
|
+
else
|
|
11
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Colors
|
|
15
|
+
GREEN='\033[0;32m'
|
|
16
|
+
BLUE='\033[0;34m'
|
|
17
|
+
YELLOW='\033[1;33m'
|
|
18
|
+
RED='\033[0;31m'
|
|
19
|
+
CYAN='\033[0;36m'
|
|
20
|
+
MAGENTA='\033[0;35m'
|
|
21
|
+
NC='\033[0m'
|
|
22
|
+
|
|
23
|
+
echo -e "${MAGENTA}╔════════════════════════════════════════════════════════════════╗${NC}"
|
|
24
|
+
echo -e "${MAGENTA}║ ${BLUE}gimme-the-lint Progressive Linting Dashboard${MAGENTA} ║${NC}"
|
|
25
|
+
echo -e "${MAGENTA}║ ${CYAN}(Directory-Chunked, Production Code Only)${MAGENTA} ║${NC}"
|
|
26
|
+
echo -e "${MAGENTA}╚════════════════════════════════════════════════════════════════╝${NC}"
|
|
27
|
+
echo ""
|
|
28
|
+
echo -e "${YELLOW}Generated:${NC} $(date '+%Y-%m-%d %H:%M:%S')"
|
|
29
|
+
echo ""
|
|
30
|
+
|
|
31
|
+
cd "$PROJECT_ROOT"
|
|
32
|
+
|
|
33
|
+
# Detect project structure
|
|
34
|
+
FRONTEND_DIR=""
|
|
35
|
+
BACKEND_DIR=""
|
|
36
|
+
|
|
37
|
+
if [ -d "frontend/src" ]; then
|
|
38
|
+
FRONTEND_DIR="frontend"
|
|
39
|
+
elif [ -d "src" ] && [ -f "package.json" ]; then
|
|
40
|
+
FRONTEND_DIR="."
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [ -d "backend/app" ]; then
|
|
44
|
+
BACKEND_DIR="backend"
|
|
45
|
+
elif [ -d "app" ] && ([ -f "pyproject.toml" ] || [ -f "requirements.txt" ]); then
|
|
46
|
+
BACKEND_DIR="."
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Helper: show manifest status
|
|
50
|
+
show_manifest_status() {
|
|
51
|
+
local label="$1"
|
|
52
|
+
local manifest_path="$2"
|
|
53
|
+
local src_path="$3"
|
|
54
|
+
local config_file="$4"
|
|
55
|
+
|
|
56
|
+
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
57
|
+
echo -e "${CYAN}${label}${NC}"
|
|
58
|
+
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
59
|
+
echo ""
|
|
60
|
+
|
|
61
|
+
if [ -f "$manifest_path" ]; then
|
|
62
|
+
echo -e "${GREEN}✓${NC} Baselines active"
|
|
63
|
+
|
|
64
|
+
CREATED_AT=$(jq -r '.created_at' "$manifest_path" 2>/dev/null || echo "unknown")
|
|
65
|
+
TOTAL_DIRS=$(jq -r '.total_directories' "$manifest_path" 2>/dev/null || echo "0")
|
|
66
|
+
TOTAL_VIOLS=$(jq -r '.total_violations' "$manifest_path" 2>/dev/null || echo "0")
|
|
67
|
+
|
|
68
|
+
# Calculate age
|
|
69
|
+
BASELINE_AGE="unknown"
|
|
70
|
+
if command -v python3 &>/dev/null && [ "$CREATED_AT" != "unknown" ]; then
|
|
71
|
+
BASELINE_AGE=$(python3 -c "
|
|
72
|
+
from datetime import datetime
|
|
73
|
+
try:
|
|
74
|
+
created = datetime.fromisoformat('$CREATED_AT'.replace('Z', '+00:00'))
|
|
75
|
+
print((datetime.now(created.tzinfo) - created).days)
|
|
76
|
+
except:
|
|
77
|
+
print('unknown')
|
|
78
|
+
" 2>/dev/null || echo "unknown")
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
echo " Created: $CREATED_AT (${BASELINE_AGE} days ago)"
|
|
82
|
+
echo " Directories: $TOTAL_DIRS baselined"
|
|
83
|
+
echo " Violations: $TOTAL_VIOLS"
|
|
84
|
+
|
|
85
|
+
# Directory drift
|
|
86
|
+
if [ -d "$src_path" ]; then
|
|
87
|
+
CURRENT_DIRS=$(find "$src_path" -mindepth 1 -maxdepth 1 -type d ! -name "__pycache__" ! -name "__tests__" ! -name "testing" ! -name "e2e" ! -name "node_modules" 2>/dev/null | grep -v -i test | wc -l)
|
|
88
|
+
if [ "$CURRENT_DIRS" -gt "$TOTAL_DIRS" ]; then
|
|
89
|
+
DRIFT=$((CURRENT_DIRS - TOTAL_DIRS))
|
|
90
|
+
echo -e " ${YELLOW}⚠ Drift: +${DRIFT} new directories detected${NC}"
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Time drift
|
|
95
|
+
if [ "$BASELINE_AGE" != "unknown" ] && [ "$BASELINE_AGE" -gt 30 ]; then
|
|
96
|
+
echo -e " ${YELLOW}⚠ Baseline is ${BASELINE_AGE} days old${NC}"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Config drift
|
|
100
|
+
if [ -f "$config_file" ]; then
|
|
101
|
+
CURRENT_HASH=$(md5sum "$config_file" 2>/dev/null | awk '{print $1}')
|
|
102
|
+
BASELINE_HASH=$(jq -r '.config_hash' "$manifest_path" 2>/dev/null)
|
|
103
|
+
if [ -n "$CURRENT_HASH" ] && [ "$CURRENT_HASH" != "$BASELINE_HASH" ]; then
|
|
104
|
+
echo -e " ${YELLOW}⚠ Linter config changed since baseline${NC}"
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
else
|
|
108
|
+
echo -e "${YELLOW}⊘${NC} No baselines created yet"
|
|
109
|
+
echo " Run: gimme-the-lint baseline"
|
|
110
|
+
fi
|
|
111
|
+
echo ""
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Frontend section
|
|
115
|
+
if [ -n "$FRONTEND_DIR" ]; then
|
|
116
|
+
FRONTEND_MANIFEST="${PROJECT_ROOT}/${FRONTEND_DIR}/.lttf/.baseline-manifest.json"
|
|
117
|
+
FRONTEND_SRC="${PROJECT_ROOT}/${FRONTEND_DIR}/src"
|
|
118
|
+
FRONTEND_CONFIG="${PROJECT_ROOT}/${FRONTEND_DIR}/eslint.config.js"
|
|
119
|
+
show_manifest_status "Frontend (ESLint)" "$FRONTEND_MANIFEST" "$FRONTEND_SRC" "$FRONTEND_CONFIG"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Backend section
|
|
123
|
+
if [ -n "$BACKEND_DIR" ]; then
|
|
124
|
+
BACKEND_MANIFEST="${PROJECT_ROOT}/${BACKEND_DIR}/.lttf-ruff/.baseline-manifest.json"
|
|
125
|
+
BACKEND_SRC="${PROJECT_ROOT}/${BACKEND_DIR}/app"
|
|
126
|
+
BACKEND_CONFIG="${PROJECT_ROOT}/pyproject.toml"
|
|
127
|
+
show_manifest_status "Backend (Ruff)" "$BACKEND_MANIFEST" "$BACKEND_SRC" "$BACKEND_CONFIG"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# No linting detected
|
|
131
|
+
if [ -z "$FRONTEND_DIR" ] && [ -z "$BACKEND_DIR" ]; then
|
|
132
|
+
echo -e "${YELLOW}No frontend or backend directories detected.${NC}"
|
|
133
|
+
echo " Expected: frontend/src/ or src/ (JS/TS)"
|
|
134
|
+
echo " Expected: backend/app/ or app/ (Python)"
|
|
135
|
+
echo ""
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Overall summary
|
|
139
|
+
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
140
|
+
echo -e "${MAGENTA}Overall Status${NC}"
|
|
141
|
+
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
142
|
+
echo ""
|
|
143
|
+
|
|
144
|
+
has_baselines=false
|
|
145
|
+
if [ -n "$FRONTEND_DIR" ] && [ -f "${PROJECT_ROOT}/${FRONTEND_DIR}/.lttf/.baseline-manifest.json" ]; then
|
|
146
|
+
has_baselines=true
|
|
147
|
+
fi
|
|
148
|
+
if [ -n "$BACKEND_DIR" ] && [ -f "${PROJECT_ROOT}/${BACKEND_DIR}/.lttf-ruff/.baseline-manifest.json" ]; then
|
|
149
|
+
has_baselines=true
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
if [ "$has_baselines" = true ]; then
|
|
153
|
+
echo -e "${GREEN}✓ Progressive linting is active${NC}"
|
|
154
|
+
echo ""
|
|
155
|
+
echo -e "${BLUE}Commands:${NC}"
|
|
156
|
+
echo " gimme-the-lint check Run progressive checks"
|
|
157
|
+
echo " gimme-the-lint check --fix Auto-fix violations"
|
|
158
|
+
echo " gimme-the-lint baseline Refresh baselines"
|
|
159
|
+
else
|
|
160
|
+
echo -e "${YELLOW}⚠ No baselines found${NC}"
|
|
161
|
+
echo ""
|
|
162
|
+
echo -e "${BLUE}Quick start:${NC}"
|
|
163
|
+
echo " gimme-the-lint install Initialize configs"
|
|
164
|
+
echo " gimme-the-lint baseline Create baselines"
|
|
165
|
+
echo " gimme-the-lint hooks Install git hooks"
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
echo ""
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gimme-the-lint: ESLint Baseline Creator (Directory-Chunked, Test-Excluding)
|
|
3
|
+
# Purpose: Create baselines per directory for progressive linting (production code only)
|
|
4
|
+
# Usage: ./scripts/eslint-baseline.sh [directory]
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
if [ -n "$GIMME_PROJECT_ROOT" ]; then
|
|
9
|
+
PROJECT_ROOT="$GIMME_PROJECT_ROOT"
|
|
10
|
+
else
|
|
11
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Colors
|
|
15
|
+
GREEN='\033[0;32m'
|
|
16
|
+
BLUE='\033[0;34m'
|
|
17
|
+
YELLOW='\033[1;33m'
|
|
18
|
+
RED='\033[0;31m'
|
|
19
|
+
CYAN='\033[0;36m'
|
|
20
|
+
NC='\033[0m'
|
|
21
|
+
|
|
22
|
+
echo -e "${BLUE}gimme-the-lint: ESLint Baseline Creator (Directory-Chunked)${NC}"
|
|
23
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
24
|
+
|
|
25
|
+
# Detect frontend directory
|
|
26
|
+
if [ -d "${PROJECT_ROOT}/frontend/src" ]; then
|
|
27
|
+
FRONTEND_DIR="${PROJECT_ROOT}/frontend"
|
|
28
|
+
SRC_DIR="src"
|
|
29
|
+
elif [ -d "${PROJECT_ROOT}/src" ] && [ -f "${PROJECT_ROOT}/package.json" ]; then
|
|
30
|
+
FRONTEND_DIR="${PROJECT_ROOT}"
|
|
31
|
+
SRC_DIR="src"
|
|
32
|
+
else
|
|
33
|
+
echo -e "${RED}✗ No frontend src directory found${NC}"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
LTTF_DIR="${FRONTEND_DIR}/.lttf"
|
|
38
|
+
|
|
39
|
+
# Check target
|
|
40
|
+
if [ -n "$1" ]; then
|
|
41
|
+
TARGET_DIR="$1"
|
|
42
|
+
echo -e "${YELLOW}Mode:${NC} Single directory baseline"
|
|
43
|
+
echo -e "${YELLOW}Target:${NC} ${SRC_DIR}/${TARGET_DIR}"
|
|
44
|
+
else
|
|
45
|
+
TARGET_DIR=""
|
|
46
|
+
echo -e "${YELLOW}Mode:${NC} Full codebase baseline"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
cd "$FRONTEND_DIR"
|
|
50
|
+
mkdir -p "$LTTF_DIR"
|
|
51
|
+
|
|
52
|
+
# Auto-discover directories (exclude test directories)
|
|
53
|
+
echo -e "${BLUE}Auto-discovering directories in ${SRC_DIR}/ (excluding test directories)...${NC}"
|
|
54
|
+
mapfile -t ALL_DIRS < <(find "$SRC_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; 2>/dev/null | sort)
|
|
55
|
+
|
|
56
|
+
DIRECTORIES=()
|
|
57
|
+
for dir in "${ALL_DIRS[@]}"; do
|
|
58
|
+
if [[ ! "$dir" =~ [Tt]est ]] && [[ "$dir" != "e2e" ]] && [[ "$dir" != "__tests__" ]] && [[ "$dir" != "node_modules" ]]; then
|
|
59
|
+
DIRECTORIES+=("$dir")
|
|
60
|
+
else
|
|
61
|
+
echo -e "${YELLOW}⊘${NC} Excluding: ${dir}"
|
|
62
|
+
fi
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
if [ ${#DIRECTORIES[@]} -eq 0 ]; then
|
|
66
|
+
echo -e "${RED}✗ No production directories found in ${SRC_DIR}/${NC}"
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
echo -e "${GREEN}✓${NC} Found ${#DIRECTORIES[@]} production directories: ${DIRECTORIES[*]}"
|
|
71
|
+
|
|
72
|
+
# Baseline function
|
|
73
|
+
create_directory_baseline() {
|
|
74
|
+
local dir=$1
|
|
75
|
+
local dir_path="${SRC_DIR}/${dir}"
|
|
76
|
+
|
|
77
|
+
if [ ! -d "$dir_path" ]; then
|
|
78
|
+
echo -e "${YELLOW}⚠${NC} Skipping ${dir} (not found)"
|
|
79
|
+
return
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
echo -e "\n${BLUE}Processing: ${dir}${NC}"
|
|
83
|
+
|
|
84
|
+
if command -v npx &>/dev/null && npx lttf --version &>/dev/null 2>&1; then
|
|
85
|
+
if npx lttf ignore --filter "${dir_path}/**/*.{js,jsx,ts,tsx}" 2>&1 | grep -v "^$"; then
|
|
86
|
+
echo -e "${GREEN}✓${NC} Baseline created for ${dir}"
|
|
87
|
+
else
|
|
88
|
+
echo -e "${YELLOW}⚠${NC} No violations found in ${dir}"
|
|
89
|
+
fi
|
|
90
|
+
else
|
|
91
|
+
echo -e "${YELLOW}⚠${NC} lint-to-the-future not available, using eslint JSON output"
|
|
92
|
+
local baseline_file="${LTTF_DIR}/baseline-${dir}.json"
|
|
93
|
+
|
|
94
|
+
if npx eslint "${dir_path}" --format=json > "$baseline_file" 2>/dev/null; then
|
|
95
|
+
echo -e "${GREEN}✓${NC} No violations in ${dir}"
|
|
96
|
+
rm -f "$baseline_file"
|
|
97
|
+
else
|
|
98
|
+
local violation_count=$(jq '[.[] | .messages | length] | add // 0' "$baseline_file" 2>/dev/null || echo "0")
|
|
99
|
+
if [ "$violation_count" = "0" ]; then
|
|
100
|
+
echo -e "${GREEN}✓${NC} No violations in ${dir}"
|
|
101
|
+
rm -f "$baseline_file"
|
|
102
|
+
else
|
|
103
|
+
echo -e "${GREEN}✓${NC} Baseline created: ${violation_count} violations in ${dir}"
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Drift detection (compare old manifest)
|
|
110
|
+
MANIFEST_FILE="${LTTF_DIR}/.baseline-manifest.json"
|
|
111
|
+
DRIFT_LOGGED=false
|
|
112
|
+
|
|
113
|
+
if [ -f "$MANIFEST_FILE" ]; then
|
|
114
|
+
OLD_DIRS=$(jq -r '.directories_baselined[]' "$MANIFEST_FILE" 2>/dev/null | sort)
|
|
115
|
+
OLD_CONFIG_HASH=$(jq -r '.config_hash' "$MANIFEST_FILE" 2>/dev/null)
|
|
116
|
+
OLD_VIOLATIONS=$(jq -r '.total_violations' "$MANIFEST_FILE" 2>/dev/null)
|
|
117
|
+
OLD_TIMESTAMP=$(jq -r '.created_at' "$MANIFEST_FILE" 2>/dev/null)
|
|
118
|
+
|
|
119
|
+
NEW_DIRS=$(printf '%s\n' "${DIRECTORIES[@]}" | sort)
|
|
120
|
+
|
|
121
|
+
ESLINT_CONFIG=""
|
|
122
|
+
for cfg in eslint.config.js eslint.config.mjs .eslintrc.js .eslintrc.json; do
|
|
123
|
+
if [ -f "$cfg" ]; then
|
|
124
|
+
ESLINT_CONFIG="$cfg"
|
|
125
|
+
break
|
|
126
|
+
fi
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
NEW_CONFIG_HASH="unknown"
|
|
130
|
+
if [ -n "$ESLINT_CONFIG" ]; then
|
|
131
|
+
NEW_CONFIG_HASH=$(md5sum "$ESLINT_CONFIG" 2>/dev/null | awk '{print $1}' || echo "unknown")
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
ADDED_DIRS=$(comm -13 <(echo "$OLD_DIRS") <(echo "$NEW_DIRS"))
|
|
135
|
+
REMOVED_DIRS=$(comm -23 <(echo "$OLD_DIRS") <(echo "$NEW_DIRS"))
|
|
136
|
+
CONFIG_CHANGED=false
|
|
137
|
+
[ "$OLD_CONFIG_HASH" != "$NEW_CONFIG_HASH" ] && CONFIG_CHANGED=true
|
|
138
|
+
|
|
139
|
+
if [ -n "$ADDED_DIRS" ] || [ -n "$REMOVED_DIRS" ] || [ "$CONFIG_CHANGED" = true ]; then
|
|
140
|
+
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
141
|
+
echo -e "${CYAN}Auto-Healing: Baseline Drift Detected${NC}"
|
|
142
|
+
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
143
|
+
echo -e "${BLUE}Previous baseline:${NC} $OLD_TIMESTAMP"
|
|
144
|
+
DRIFT_LOGGED=true
|
|
145
|
+
|
|
146
|
+
[ -n "$ADDED_DIRS" ] && echo -e "${GREEN}+ Added:${NC}" && echo "$ADDED_DIRS" | while read -r d; do echo " $d"; done
|
|
147
|
+
[ -n "$REMOVED_DIRS" ] && echo -e "${RED}- Removed:${NC}" && echo "$REMOVED_DIRS" | while read -r d; do echo " $d"; done
|
|
148
|
+
[ "$CONFIG_CHANGED" = true ] && echo -e "${YELLOW}⚙ ESLint config changed${NC}"
|
|
149
|
+
echo ""
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# Main execution
|
|
154
|
+
if [ -n "$TARGET_DIR" ]; then
|
|
155
|
+
create_directory_baseline "$TARGET_DIR"
|
|
156
|
+
else
|
|
157
|
+
echo -e "\n${BLUE}Creating baselines for all production directories...${NC}"
|
|
158
|
+
for dir in "${DIRECTORIES[@]}"; do
|
|
159
|
+
create_directory_baseline "$dir"
|
|
160
|
+
done
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
echo -e "\n${YELLOW}Note:${NC} Test directories excluded from baselines"
|
|
164
|
+
echo " Excluded: __tests__, testing, e2e, *test*"
|
|
165
|
+
|
|
166
|
+
# Create/update manifest
|
|
167
|
+
ESLINT_CONFIG=""
|
|
168
|
+
for cfg in eslint.config.js eslint.config.mjs .eslintrc.js .eslintrc.json; do
|
|
169
|
+
if [ -f "$cfg" ]; then
|
|
170
|
+
ESLINT_CONFIG="$cfg"
|
|
171
|
+
break
|
|
172
|
+
fi
|
|
173
|
+
done
|
|
174
|
+
|
|
175
|
+
CONFIG_HASH="unknown"
|
|
176
|
+
if [ -n "$ESLINT_CONFIG" ]; then
|
|
177
|
+
CONFIG_HASH=$(md5sum "$ESLINT_CONFIG" 2>/dev/null | awk '{print $1}' || echo "unknown")
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
LTTF_VERSION=$(npx lttf --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' || echo 'unknown')
|
|
181
|
+
|
|
182
|
+
TOTAL_VIOLATIONS=0
|
|
183
|
+
if [ -d "$LTTF_DIR" ]; then
|
|
184
|
+
for f in "$LTTF_DIR"/*.lint-todo "$LTTF_DIR"/baseline-*.json; do
|
|
185
|
+
if [ -f "$f" ]; then
|
|
186
|
+
if [[ "$f" == *.lint-todo ]]; then
|
|
187
|
+
count=$(wc -l < "$f" 2>/dev/null || echo "0")
|
|
188
|
+
else
|
|
189
|
+
count=$(jq '[.[] | .messages | length] | add // 0' "$f" 2>/dev/null || echo "0")
|
|
190
|
+
fi
|
|
191
|
+
TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + count))
|
|
192
|
+
fi
|
|
193
|
+
done
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
cat > "$MANIFEST_FILE" <<EOF
|
|
197
|
+
{
|
|
198
|
+
"created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
199
|
+
"tool": "eslint",
|
|
200
|
+
"version": "$LTTF_VERSION",
|
|
201
|
+
"directories_baselined": $(printf '%s\n' "${DIRECTORIES[@]}" | jq -R . | jq -s .),
|
|
202
|
+
"total_directories": ${#DIRECTORIES[@]},
|
|
203
|
+
"total_violations": $TOTAL_VIOLATIONS,
|
|
204
|
+
"config_hash": "$CONFIG_HASH",
|
|
205
|
+
"test_excluded": ["__tests__", "testing", "e2e", "*.test.*", "*.spec.*"]
|
|
206
|
+
}
|
|
207
|
+
EOF
|
|
208
|
+
|
|
209
|
+
echo -e "\n${GREEN}✓${NC} Manifest updated: $MANIFEST_FILE"
|
|
210
|
+
echo " Directories: ${#DIRECTORIES[@]} | Violations: $TOTAL_VIOLATIONS"
|
|
211
|
+
|
|
212
|
+
if [ "$DRIFT_LOGGED" = true ]; then
|
|
213
|
+
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
214
|
+
echo -e "${GREEN}✓ Auto-Healing Complete${NC}"
|
|
215
|
+
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
216
|
+
|
|
217
|
+
if [ -n "$OLD_VIOLATIONS" ] && [ "$TOTAL_VIOLATIONS" != "$OLD_VIOLATIONS" ]; then
|
|
218
|
+
DIFF=$((TOTAL_VIOLATIONS - OLD_VIOLATIONS))
|
|
219
|
+
if [ $DIFF -gt 0 ]; then
|
|
220
|
+
echo -e "${YELLOW}Violations increased: +$DIFF${NC}"
|
|
221
|
+
else
|
|
222
|
+
echo -e "${GREEN}Violations decreased: $DIFF${NC}"
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
echo ""
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gimme-the-lint: Ruff Baseline Creator (Directory-Chunked, Test-Excluding)
|
|
3
|
+
# Purpose: Create Ruff baselines per directory for progressive linting (production code only)
|
|
4
|
+
# Usage: ./scripts/ruff-baseline.sh [directory]
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
if [ -n "$GIMME_PROJECT_ROOT" ]; then
|
|
9
|
+
PROJECT_ROOT="$GIMME_PROJECT_ROOT"
|
|
10
|
+
else
|
|
11
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Colors
|
|
15
|
+
GREEN='\033[0;32m'
|
|
16
|
+
BLUE='\033[0;34m'
|
|
17
|
+
YELLOW='\033[1;33m'
|
|
18
|
+
RED='\033[0;31m'
|
|
19
|
+
CYAN='\033[0;36m'
|
|
20
|
+
NC='\033[0m'
|
|
21
|
+
|
|
22
|
+
echo -e "${BLUE}gimme-the-lint: Ruff Baseline Creator (Directory-Chunked)${NC}"
|
|
23
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
24
|
+
|
|
25
|
+
# Detect backend directory
|
|
26
|
+
if [ -d "${PROJECT_ROOT}/backend/app" ]; then
|
|
27
|
+
BACKEND_DIR="${PROJECT_ROOT}/backend"
|
|
28
|
+
APP_DIR="app"
|
|
29
|
+
elif [ -d "${PROJECT_ROOT}/app" ]; then
|
|
30
|
+
BACKEND_DIR="${PROJECT_ROOT}"
|
|
31
|
+
APP_DIR="app"
|
|
32
|
+
else
|
|
33
|
+
echo -e "${RED}✗ No backend app directory found${NC}"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
LTTF_RUFF_DIR="${BACKEND_DIR}/.lttf-ruff"
|
|
38
|
+
|
|
39
|
+
if [ -n "$1" ]; then
|
|
40
|
+
TARGET_DIR="$1"
|
|
41
|
+
echo -e "${YELLOW}Mode:${NC} Single directory baseline"
|
|
42
|
+
echo -e "${YELLOW}Target:${NC} ${APP_DIR}/${TARGET_DIR}"
|
|
43
|
+
else
|
|
44
|
+
TARGET_DIR=""
|
|
45
|
+
echo -e "${YELLOW}Mode:${NC} Full codebase baseline"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
cd "$BACKEND_DIR"
|
|
49
|
+
mkdir -p "$LTTF_RUFF_DIR"
|
|
50
|
+
|
|
51
|
+
# Check for venv and ruff
|
|
52
|
+
VENV_ACTIVATE="${PROJECT_ROOT}/.venv/bin/activate"
|
|
53
|
+
if [ ! -f "$VENV_ACTIVATE" ]; then
|
|
54
|
+
echo -e "${RED}✗ Python .venv not found${NC}"
|
|
55
|
+
echo " Run: gimme-the-lint venv setup"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
source "$VENV_ACTIVATE"
|
|
60
|
+
|
|
61
|
+
if ! command -v ruff &> /dev/null; then
|
|
62
|
+
echo -e "${RED}✗ Ruff not installed in .venv${NC}"
|
|
63
|
+
echo " Run: gimme-the-lint venv setup"
|
|
64
|
+
deactivate
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
echo -e "${GREEN}✓${NC} Ruff found: $(ruff --version)"
|
|
69
|
+
|
|
70
|
+
# Auto-discover directories
|
|
71
|
+
echo -e "${BLUE}Auto-discovering directories in ${APP_DIR}/ (excluding test directories)...${NC}"
|
|
72
|
+
mapfile -t ALL_DIRS < <(find "$APP_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; 2>/dev/null | sort)
|
|
73
|
+
|
|
74
|
+
DIRECTORIES=()
|
|
75
|
+
for dir in "${ALL_DIRS[@]}"; do
|
|
76
|
+
if [[ ! "$dir" =~ [Tt]est ]] && [[ "$dir" != "__pycache__" ]]; then
|
|
77
|
+
DIRECTORIES+=("$dir")
|
|
78
|
+
else
|
|
79
|
+
echo -e "${YELLOW}⊘${NC} Excluding: ${dir}"
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
if [ ${#DIRECTORIES[@]} -eq 0 ]; then
|
|
84
|
+
echo -e "${RED}✗ No production directories found in ${APP_DIR}/${NC}"
|
|
85
|
+
deactivate
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
echo -e "${GREEN}✓${NC} Found ${#DIRECTORIES[@]} production directories: ${DIRECTORIES[*]}"
|
|
90
|
+
|
|
91
|
+
# Baseline function
|
|
92
|
+
create_directory_baseline() {
|
|
93
|
+
local dir=$1
|
|
94
|
+
local dir_path="${APP_DIR}/${dir}"
|
|
95
|
+
|
|
96
|
+
if [ ! -d "$dir_path" ]; then
|
|
97
|
+
echo -e "${YELLOW}⚠${NC} Skipping ${dir} (not found)"
|
|
98
|
+
return
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
echo -e "\n${BLUE}Processing: ${dir}${NC}"
|
|
102
|
+
|
|
103
|
+
local py_count=$(find "$dir_path" -name "*.py" ! -name "test_*.py" ! -name "*_test.py" 2>/dev/null | wc -l)
|
|
104
|
+
if [ "$py_count" -eq 0 ]; then
|
|
105
|
+
echo -e "${YELLOW}⚠${NC} No Python files in ${dir}"
|
|
106
|
+
return
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
local baseline_file="${LTTF_RUFF_DIR}/baseline-${dir}.json"
|
|
110
|
+
|
|
111
|
+
if ruff check "$dir_path" \
|
|
112
|
+
--output-format=json \
|
|
113
|
+
--exclude "test_*.py" \
|
|
114
|
+
--exclude "*_test.py" \
|
|
115
|
+
> "$baseline_file" 2>/dev/null; then
|
|
116
|
+
echo -e "${GREEN}✓${NC} No violations in ${dir} (${py_count} files)"
|
|
117
|
+
rm -f "$baseline_file"
|
|
118
|
+
else
|
|
119
|
+
local violation_count=$(jq '. | length' "$baseline_file" 2>/dev/null || echo "0")
|
|
120
|
+
if [ "$violation_count" = "0" ] || [ -z "$violation_count" ]; then
|
|
121
|
+
echo -e "${GREEN}✓${NC} No violations in ${dir} (${py_count} files)"
|
|
122
|
+
rm -f "$baseline_file"
|
|
123
|
+
else
|
|
124
|
+
echo -e "${GREEN}✓${NC} Baseline: ${violation_count} violations in ${dir}"
|
|
125
|
+
|
|
126
|
+
local temp_file="${baseline_file}.tmp"
|
|
127
|
+
jq --arg dir "$dir" --arg count "$violation_count" --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
128
|
+
'. | {directory: $dir, violation_count: ($count | tonumber), created_at: $date, violations: .}' \
|
|
129
|
+
"$baseline_file" > "$temp_file" && mv "$temp_file" "$baseline_file"
|
|
130
|
+
fi
|
|
131
|
+
fi
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Drift detection
|
|
135
|
+
MANIFEST_FILE="${LTTF_RUFF_DIR}/.baseline-manifest.json"
|
|
136
|
+
DRIFT_LOGGED=false
|
|
137
|
+
|
|
138
|
+
if [ -f "$MANIFEST_FILE" ]; then
|
|
139
|
+
OLD_DIRS=$(jq -r '.directories_baselined[]' "$MANIFEST_FILE" 2>/dev/null | sort)
|
|
140
|
+
OLD_CONFIG_HASH=$(jq -r '.config_hash' "$MANIFEST_FILE" 2>/dev/null)
|
|
141
|
+
OLD_VIOLATIONS=$(jq -r '.total_violations' "$MANIFEST_FILE" 2>/dev/null)
|
|
142
|
+
OLD_TIMESTAMP=$(jq -r '.created_at' "$MANIFEST_FILE" 2>/dev/null)
|
|
143
|
+
|
|
144
|
+
NEW_DIRS=$(printf '%s\n' "${DIRECTORIES[@]}" | sort)
|
|
145
|
+
|
|
146
|
+
PYPROJECT="${PROJECT_ROOT}/pyproject.toml"
|
|
147
|
+
NEW_CONFIG_HASH=$(md5sum "$PYPROJECT" 2>/dev/null | awk '{print $1}' || echo "unknown")
|
|
148
|
+
|
|
149
|
+
ADDED_DIRS=$(comm -13 <(echo "$OLD_DIRS") <(echo "$NEW_DIRS"))
|
|
150
|
+
REMOVED_DIRS=$(comm -23 <(echo "$OLD_DIRS") <(echo "$NEW_DIRS"))
|
|
151
|
+
CONFIG_CHANGED=false
|
|
152
|
+
[ "$OLD_CONFIG_HASH" != "$NEW_CONFIG_HASH" ] && CONFIG_CHANGED=true
|
|
153
|
+
|
|
154
|
+
if [ -n "$ADDED_DIRS" ] || [ -n "$REMOVED_DIRS" ] || [ "$CONFIG_CHANGED" = true ]; then
|
|
155
|
+
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
156
|
+
echo -e "${CYAN}Auto-Healing: Baseline Drift Detected${NC}"
|
|
157
|
+
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
158
|
+
echo -e "${BLUE}Previous baseline:${NC} $OLD_TIMESTAMP"
|
|
159
|
+
DRIFT_LOGGED=true
|
|
160
|
+
|
|
161
|
+
[ -n "$ADDED_DIRS" ] && echo -e "${GREEN}+ Added:${NC}" && echo "$ADDED_DIRS" | while read -r d; do echo " $d"; done
|
|
162
|
+
[ -n "$REMOVED_DIRS" ] && echo -e "${RED}- Removed:${NC}" && echo "$REMOVED_DIRS" | while read -r d; do echo " $d"; done
|
|
163
|
+
[ "$CONFIG_CHANGED" = true ] && echo -e "${YELLOW}⚙ Ruff config changed${NC}"
|
|
164
|
+
echo ""
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# Main execution
|
|
169
|
+
if [ -n "$TARGET_DIR" ]; then
|
|
170
|
+
create_directory_baseline "$TARGET_DIR"
|
|
171
|
+
else
|
|
172
|
+
echo -e "\n${BLUE}Creating baselines for all production directories...${NC}"
|
|
173
|
+
for dir in "${DIRECTORIES[@]}"; do
|
|
174
|
+
create_directory_baseline "$dir"
|
|
175
|
+
done
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
echo -e "\n${YELLOW}Note:${NC} Test directories/files excluded"
|
|
179
|
+
echo " Excluded: tests, *test*, __pycache__, test_*.py, *_test.py"
|
|
180
|
+
|
|
181
|
+
# Create/update manifest
|
|
182
|
+
PYPROJECT="${PROJECT_ROOT}/pyproject.toml"
|
|
183
|
+
CONFIG_HASH=$(md5sum "$PYPROJECT" 2>/dev/null | awk '{print $1}' || echo "unknown")
|
|
184
|
+
RUFF_VERSION=$(ruff --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' || echo 'unknown')
|
|
185
|
+
|
|
186
|
+
TOTAL_VIOLATIONS=0
|
|
187
|
+
for baseline_file in "$LTTF_RUFF_DIR"/baseline-*.json; do
|
|
188
|
+
if [ -f "$baseline_file" ]; then
|
|
189
|
+
viol_count=$(jq -r '.violation_count' "$baseline_file" 2>/dev/null || echo "0")
|
|
190
|
+
TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + viol_count))
|
|
191
|
+
fi
|
|
192
|
+
done
|
|
193
|
+
|
|
194
|
+
cat > "$MANIFEST_FILE" <<EOF
|
|
195
|
+
{
|
|
196
|
+
"created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
197
|
+
"tool": "ruff",
|
|
198
|
+
"version": "$RUFF_VERSION",
|
|
199
|
+
"directories_baselined": $(printf '%s\n' "${DIRECTORIES[@]}" | jq -R . | jq -s .),
|
|
200
|
+
"total_directories": ${#DIRECTORIES[@]},
|
|
201
|
+
"total_violations": $TOTAL_VIOLATIONS,
|
|
202
|
+
"config_hash": "$CONFIG_HASH",
|
|
203
|
+
"test_excluded": ["tests", "*test*", "__pycache__"]
|
|
204
|
+
}
|
|
205
|
+
EOF
|
|
206
|
+
|
|
207
|
+
echo -e "\n${GREEN}✓${NC} Manifest updated: $MANIFEST_FILE"
|
|
208
|
+
echo " Directories: ${#DIRECTORIES[@]} | Violations: $TOTAL_VIOLATIONS"
|
|
209
|
+
|
|
210
|
+
if [ "$DRIFT_LOGGED" = true ]; then
|
|
211
|
+
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
212
|
+
echo -e "${GREEN}✓ Auto-Healing Complete${NC}"
|
|
213
|
+
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
214
|
+
|
|
215
|
+
if [ -n "$OLD_VIOLATIONS" ] && [ "$TOTAL_VIOLATIONS" != "$OLD_VIOLATIONS" ]; then
|
|
216
|
+
DIFF=$((TOTAL_VIOLATIONS - OLD_VIOLATIONS))
|
|
217
|
+
if [ $DIFF -gt 0 ]; then
|
|
218
|
+
echo -e "${YELLOW}Violations increased: +$DIFF${NC}"
|
|
219
|
+
else
|
|
220
|
+
echo -e "${GREEN}Violations decreased: $DIFF${NC}"
|
|
221
|
+
fi
|
|
222
|
+
fi
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
deactivate
|
|
226
|
+
echo ""
|