Skip to content

Commit 9605e12

Browse files
authored
Merge pull request #13 from Hypercart-Dev-Tools/docs/fix-readme
Docs/fix readme to Development
2 parents 3fb0292 + 2808697 commit 9605e12

3 files changed

Lines changed: 394 additions & 23 deletions

File tree

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
# Severity Ranking MVP v1
2+
3+
**Context**: WP Code Check is a CLI scanner that detects 15+ WordPress performance/security antipatterns. It already ships hardcoded checks with fixed severity levels (CRITICAL, HIGH, MEDIUM, LOW). This feature allows teams to customize severity rankings per-project to reduce noise and focus on what matters to them.
4+
5+
## Problem
6+
- **Developer A** thinks missing nonce checks are CRITICAL (security team)
7+
- **Developer B** downranks them to MEDIUM (code review overhead)
8+
- **Developer C** wants `deprecated-function` as LOW (legacy codebase)
9+
10+
Currently: All teams get the same severity levels. No customization = noise.
11+
12+
## The MVP Approach: Shipped Config + Local Override
13+
14+
**Core Concept**: Ship a `/dist/config/severity-levels.json` with the project. Users copy it locally, customize it, and push to CI/CD. Factory defaults live in the file as an escape hatch.
15+
16+
### Current State (Before MVP)
17+
18+
The script `./dist/bin/check-performance.sh` has hardcoded severity levels:
19+
20+
```bash
21+
# Current (in script, not configurable)
22+
text_echo "${BLUE}▸ Missing nonce validation ${RED}[HIGH]${NC}"
23+
text_echo "${BLUE}▸ REST endpoints without pagination ${RED}[CRITICAL]${NC}"
24+
text_echo "${BLUE}▸ Unbounded queries ${RED}[CRITICAL]${NC}"
25+
text_echo "${BLUE}▸ N+1 patterns ${YELLOW}[MEDIUM]${NC}"
26+
```
27+
28+
### MVP: Shipped Config File
29+
30+
**Phase 1: Create `/dist/config/severity-levels.json`**
31+
32+
```json
33+
{
34+
"_metadata": {
35+
"version": "1.0.59",
36+
"description": "WP Code Check - Severity Level Customization",
37+
"last_updated": "2025-12-31"
38+
},
39+
"severity_levels": {
40+
"wp-ajax-missing-nonce": {
41+
"id": "wp-ajax-missing-nonce",
42+
"level": "HIGH",
43+
"factory_default": "HIGH",
44+
"category": "security",
45+
"description": "AJAX handler missing nonce validation"
46+
},
47+
"unbounded-rest-endpoint": {
48+
"id": "unbounded-rest-endpoint",
49+
"level": "CRITICAL",
50+
"factory_default": "CRITICAL",
51+
"category": "performance",
52+
"description": "REST endpoint without pagination/limits"
53+
},
54+
"unbounded-query-get-posts": {
55+
"id": "unbounded-query-get-posts",
56+
"level": "CRITICAL",
57+
"factory_default": "CRITICAL",
58+
"category": "performance",
59+
"description": "WP_Query with unbounded posts_per_page"
60+
},
61+
"n-plus-one-pattern": {
62+
"id": "n-plus-one-pattern",
63+
"level": "MEDIUM",
64+
"factory_default": "MEDIUM",
65+
"category": "performance",
66+
"description": "Potential N+1 pattern (get_post_meta in loop)"
67+
},
68+
"deprecated-function": {
69+
"id": "deprecated-function",
70+
"level": "MEDIUM",
71+
"factory_default": "MEDIUM",
72+
"category": "maintenance",
73+
"description": "Using deprecated WordPress function"
74+
}
75+
}
76+
}
77+
```
78+
79+
**Phase 2: How Users Customize**
80+
81+
```bash
82+
# Step 1: Copy shipped config to project root or ~/.wp-code-check.json
83+
cp ./dist/config/severity-levels.json ./.wp-code-check-severity.json
84+
85+
# Step 2: Edit locally (change "level" field only)
86+
# Change deprecated-function from MEDIUM to LOW:
87+
# "deprecated-function": {
88+
# "level": "LOW", <- Edit this
89+
# "factory_default": "MEDIUM" <- Leave this for reference
90+
91+
# Step 3: Run scanner with custom config
92+
./dist/bin/check-performance.sh --paths . --severity-config ./.wp-code-check-severity.json
93+
94+
# Step 4: If you break it, just check factory_default in the file
95+
# Or delete the file to use shipped defaults
96+
```
97+
98+
**How It Works in the Script:**
99+
100+
```bash
101+
# 1. Load shipped defaults
102+
load_severity_defaults()
103+
104+
# 2. Load user config if it exists (overrides shipped)
105+
if [ -f "$SEVERITY_CONFIG_FILE" ]; then
106+
CUSTOM_LEVELS=$(load_custom_severity_config "$SEVERITY_CONFIG_FILE")
107+
fi
108+
109+
# 3. Resolve final severity for display
110+
get_severity_for_check() {
111+
local check_id="$1"
112+
# Return custom > shipped
113+
echo "${CUSTOM_LEVELS[$check_id]:-${SHIPPED_LEVELS[$check_id]}}"
114+
}
115+
```
116+
117+
**Why This Works:**
118+
- ✅ Zero complexity (just JSON + array lookup)
119+
- ✅ Shipped by default (lives in `/dist/config/`)
120+
- ✅ Self-documenting (factory defaults in same file)
121+
- ✅ Version controllable (commit to repo for team alignment)
122+
- ✅ Low risk (users can always reference or delete file to reset)
123+
- ✅ Multi-location support (project-level, user-level, explicit path)
124+
- ✅ Integrates seamlessly with existing bash script (jq already used)
125+
126+
### Data Model (Minimal)
127+
128+
```bash
129+
# In the script, after loading config:
130+
declare -A SEVERITY_OVERRIDES # Custom severities loaded from file
131+
132+
# Load shipped defaults into associative array
133+
load_severity_defaults() {
134+
SEVERITY_SHIPPED["wp-ajax-missing-nonce"]="HIGH"
135+
SEVERITY_SHIPPED["unbounded-rest-endpoint"]="CRITICAL"
136+
SEVERITY_SHIPPED["unbounded-query-get-posts"]="CRITICAL"
137+
SEVERITY_SHIPPED["n-plus-one-pattern"]="MEDIUM"
138+
SEVERITY_SHIPPED["deprecated-function"]="MEDIUM"
139+
}
140+
141+
# Load custom overrides from user config file (if exists)
142+
load_custom_severity_config() {
143+
local config_file="$1"
144+
# Parse JSON and populate SEVERITY_OVERRIDES array
145+
# Use jq or simple grep/sed for portability
146+
}
147+
148+
# Get final severity (custom > shipped)
149+
get_severity() {
150+
local check_id="$1"
151+
echo "${SEVERITY_OVERRIDES[$check_id]:-${SEVERITY_SHIPPED[$check_id]}}"
152+
}
153+
```
154+
155+
### Quickest Win: 3-Day Sprint
156+
157+
**Day 1**: Extract hardcoded severity levels from `check-performance.sh` into `/dist/config/severity-levels.json`
158+
- Identify all 15+ checks and their current hardcoded levels
159+
- Create JSON structure with factory defaults
160+
- Add metadata (version, last_updated)
161+
162+
**Day 2**: Add `load_custom_severity_config()` + `get_severity()` helpers in `check-performance.sh`
163+
- Parse JSON config file with jq (already used elsewhere in script)
164+
- Merge user overrides into shipped defaults
165+
- Update all check output lines to call `get_severity()` instead of hardcoding `[CRITICAL]`
166+
- Add `--severity-config` CLI option
167+
168+
**Day 3**: Testing + documentation
169+
- Test with custom config in different locations (project root, home dir)
170+
- Verify CLI option works
171+
- Update README with usage examples
172+
- Add validation (warn if invalid severity level in config)
173+
174+
### Example Output (After MVP)
175+
176+
**TEXT REPORT:**
177+
```
178+
WP Code Check v1.0.59
179+
180+
Project: My Plugin v2.1.0 [plugin]
181+
Scanning paths: .
182+
Severity config: ./.wp-code-check-severity.json (2 customizations active)
183+
184+
━━━ CRITICAL CHECKS (will fail build) ━━━
185+
186+
▸ REST endpoints without pagination [CRITICAL]
187+
✗ FAILED
188+
./includes/api.php:42: register_rest_route()
189+
190+
▸ Unbounded queries [CRITICAL]
191+
✗ FAILED
192+
./admin/list-users.php:15: posts_per_page => -1
193+
194+
━━━ HIGH CHECKS ━━━
195+
196+
▸ AJAX missing nonce validation [HIGH → CRITICAL]
197+
✗ FAILED
198+
./assets/js/admin.js:78: jQuery.post('/wp-admin/admin-ajax.php')
199+
Note: Severity customized (factory: CRITICAL, custom: HIGH)
200+
201+
━━━ MEDIUM CHECKS ━━━
202+
203+
▸ Using deprecated functions [MEDIUM → LOW]
204+
✓ PASSED
205+
./includes/legacy.php:92: wp_make_content_safe()
206+
Note: Severity customized (factory: MEDIUM, custom: LOW)
207+
208+
━━━ SUMMARY ━━━
209+
Errors: 2 (CRITICAL checks)
210+
Warnings: 1 (HIGH checks)
211+
212+
⚠️ Severity customizations applied (2/15 checks):
213+
- wp-ajax-missing-nonce: CRITICAL → HIGH
214+
- deprecated-function: MEDIUM → LOW
215+
```
216+
217+
**JSON REPORT:**
218+
```json
219+
{
220+
"metadata": {
221+
"version": "1.0.59",
222+
"project": "My Plugin",
223+
"type": "plugin",
224+
"scan_time": "2025-12-31T15:30:45Z",
225+
"severity_config": "./.wp-code-check-severity.json",
226+
"severity_customizations": {
227+
"wp-ajax-missing-nonce": {
228+
"factory_default": "CRITICAL",
229+
"custom_level": "HIGH",
230+
"reason": "User override in project config"
231+
},
232+
"deprecated-function": {
233+
"factory_default": "MEDIUM",
234+
"custom_level": "LOW",
235+
"reason": "User override in project config"
236+
}
237+
}
238+
},
239+
"findings": [
240+
{
241+
"id": "unbounded-rest-endpoint",
242+
"severity": "CRITICAL",
243+
"severity_customized": false,
244+
"file": "./includes/api.php",
245+
"line": 42,
246+
"code": "register_rest_route()"
247+
},
248+
{
249+
"id": "wp-ajax-missing-nonce",
250+
"severity": "HIGH",
251+
"severity_customized": true,
252+
"factory_default": "CRITICAL",
253+
"file": "./assets/js/admin.js",
254+
"line": 78,
255+
"code": "jQuery.post('/wp-admin/admin-ajax.php')"
256+
}
257+
]
258+
}
259+
```
260+
261+
**HTML REPORT:**
262+
```html
263+
<div class="severity-customizations-banner">
264+
<h3>⚠️ Severity Customizations Active</h3>
265+
<p>This report uses custom severity levels. 2 out of 15 checks have been customized:</p>
266+
<table>
267+
<tr>
268+
<th>Check</th>
269+
<th>Factory Default</th>
270+
<th>Custom Level</th>
271+
</tr>
272+
<tr>
273+
<td>AJAX missing nonce</td>
274+
<td><span class="badge critical">CRITICAL</span></td>
275+
<td><span class="badge high">HIGH</span></td>
276+
</tr>
277+
<tr>
278+
<td>Deprecated functions</td>
279+
<td><span class="badge medium">MEDIUM</span></td>
280+
<td><span class="badge low">LOW</span></td>
281+
</tr>
282+
</table>
283+
<p><strong>Config file:</strong> ./.wp-code-check-severity.json</p>
284+
<button>Restore Factory Defaults</button>
285+
</div>
286+
287+
<!-- Each finding shows if severity was customized -->
288+
<div class="finding">
289+
<h4>REST endpoints without pagination <span class="badge critical">CRITICAL</span></h4>
290+
<p>File: ./includes/api.php:42</p>
291+
<p><code>register_rest_route()</code></p>
292+
<!-- No custom notice for this one -->
293+
</div>
294+
295+
<div class="finding">
296+
<h4>AJAX missing nonce <span class="badge high">HIGH</span></h4>
297+
<p>File: ./assets/js/admin.js:78</p>
298+
<p><code>jQuery.post('/wp-admin/admin-ajax.php')</code></p>
299+
<div class="custom-severity-notice">
300+
<strong>Note:</strong> Severity customized for this rule
301+
<br/>Factory default: <span class="badge critical">CRITICAL</span> → Custom: <span class="badge high">HIGH</span>
302+
</div>
303+
</div>
304+
```
305+
306+
### Rules for Users
307+
308+
1. **Where to customize**:
309+
- Copy shipped `/dist/config/severity-levels.json` to project root: `./.wp-code-check-severity.json`
310+
- Or place in home directory: `~/.wp-code-check-severity.json`
311+
- Pass explicit path: `./dist/bin/check-performance.sh --paths . --severity-config ./custom-levels.json`
312+
313+
2. **Restore to factory defaults**:
314+
- Check `"factory_default"` in the file to see what the original was
315+
- Delete your local copy to revert to shipped defaults
316+
- Or manually change `"level"` back to match `"factory_default"`
317+
318+
3. **Version control** (optional):
319+
- Commit `.wp-code-check-severity.json` to repo for team alignment
320+
- All developers on team get same severity customizations
321+
- CI/CD automatically uses the project's custom config
322+
323+
### Config File Locations (Priority Order)
324+
325+
1. `--severity-config <path>` (explicit argument, highest priority)
326+
2. `./.wp-code-check-severity.json` (project root)
327+
3. `~/.wp-code-check-severity.json` (user home)
328+
4. Shipped defaults in `/dist/config/severity-levels.json` (fallback)
329+
330+
### Implementation Checklist
331+
332+
**Phase 1 Work:**
333+
- [ ] Extract all hardcoded severity levels from `check-performance.sh` (grep for `\[CRITICAL\]`, `\[HIGH\]`, `\[MEDIUM\]`, `\[LOW\]`)
334+
- [ ] Create `/dist/config/severity-levels.json` with all checks + factory defaults
335+
- [ ] Map check patterns to rule IDs (e.g., `wp-ajax-missing-nonce`, `unbounded-rest-endpoint`)
336+
- [ ] Document each check with category (security, performance, maintenance)
337+
338+
**Phase 2 Work:**
339+
- [ ] Add `load_severity_defaults()` function to setup shipped levels
340+
- [ ] Add `load_custom_severity_config()` to parse JSON file (use jq)
341+
- [ ] Add `get_severity(rule_id)` lookup function
342+
- [ ] Track which checks are customized (array of rule_ids with custom levels)
343+
- [ ] Update all check output lines to use `get_severity()` instead of hardcoded levels
344+
- [ ] Show customization notice in text report header (e.g., "2 customizations active")
345+
- [ ] Add inline notes for customized findings (e.g., "CRITICAL → HIGH")
346+
- [ ] Add `--severity-config <path>` CLI option
347+
- [ ] Support config file discovery (project root, home dir)
348+
349+
**Phase 3 Work:**
350+
- [ ] Unit tests: verify config loading, merging, priority order
351+
- [ ] Integration tests: run with custom config, verify output includes customization notices
352+
- [ ] Text report: show banner header with active customizations + inline notes per finding
353+
- [ ] JSON report: add `severity_customizations` metadata + `severity_customized` flag per finding
354+
- [ ] HTML report: add customizations banner table + highlight customized findings with badges
355+
- [ ] Error handling: warn if config missing, invalid JSON, unknown rule IDs
356+
357+
## Future Extensions (Not MVP)
358+
359+
- ☐ Filter reports by severity threshold: `--min-severity HIGH` (only show HIGH, CRITICAL)
360+
- ☐ Fail CI only on specific severities: `--fail-on CRITICAL` (ignore MEDIUM warnings)
361+
- ☐ Team presets in config: `"team_override": { "security": {...}, "devs": {...} }`
362+
- ☐ Per-rule comments in config: `"note": "This team ignores deprecated warnings"`
363+
- ☐ HTML dashboard showing custom vs factory defaults
364+
365+
## Why Not Build This?
366+
367+
**Weighted scoring systems**: Overkill for v1, simple levels are enough
368+
**Per-file rules**: Not needed yet—most teams apply same rules project-wide
369+
**Auto-detection**: Not our job—user decides what matters to them
370+
**Database storage**: JSON files are simpler, version-controllable, mergeable
371+
**UI/CLI filters**: Start with config file, add filter options later if needed

0 commit comments

Comments
 (0)