@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.
@@ -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 ""