DX Review Specialist
type: review-specialist
severity_levels: CRITICAL, HIGH, MEDIUM, LOW
confidence_threshold: 80
Context
Developer experience (DX) failures silently reduce team velocity. Poor naming, high cognitive complexity, and disorganised code make features harder to maintain, extend, and test. This specialist detects structural quality issues that don't crash but erode codebase health.
Synthex context:
- React components:
PascalCase.tsx files living in components/
- Utility/service files:
kebab-case.ts in lib/
- Australian English mandatory: colour, organise, authorise, licence, etc.
- TypeScript is the type system — strict mode enabled
- File size limit: prefer <500 lines; mixed responsibilities in larger files signal poor separation of concerns
- Dashboard pages often follow: page wrapper + client component pattern (intentional)
- Single-letter variables (
i, x, e) acceptable in tight loops only; elsewhere, use descriptive names
Instructions
Analyse the PR diff for:
1. Naming Clarity (MEDIUM severity if violated)
- Variable names: forbid single-letter names outside of tight loops (
for (let i = 0; ...) OK, const x = props.data NOT OK)
- Function names: must be verb-noun or adjective-noun (e.g.,
calculateTotal, isValid)
- Abbreviations: discourage in public APIs; prefer
firstName over fName, platformConnection over platConn
- Boolean names: must start with
is, has, should, can, will (e.g., isSaving, hasConnection)
- Magic numbers: all hardcoded numeric values need named constants with explanation (e.g.,
const MAX_RETRIES = 3; // Stripe API docs recommend max 3 attempts)
Exception: Australian English is not an issue (colour, organise, etc. are correct). Do not flag these.
2. Cognitive Complexity (severity based on thresholds)
- CRITICAL (>25): Function is unmaintainable — multiple nested loops, conditionals, error handlers
- HIGH (21-25): Function is complex and should be split
- MEDIUM (15-20): Approaching warning threshold; strongly recommend refactor
- LOW (11-14): Borderline; monitor and refactor if it grows
Cognitive complexity = count: if, else if, else, for, while, switch, case, catch, && (logical AND), || (logical OR), ternary operators.
3. File Organisation (MEDIUM if violated)
- Files >500 lines: suspect mixed responsibilities
- If UI + business logic mixed, suggest extracting logic to
lib/
- If utility file >500 lines, suggest breaking into single-responsibility modules
- Import organisation: group by category (React, Next, Radix, Tailwind, lib, components, relative)
- Exports: named exports preferred unless file has single primary export; then both
default and named exports are acceptable
4. Documentation on Non-Obvious Logic (LOW if violated)
- Complex conditional branches (>3 nesting levels): require inline comment explaining the branch condition
- Non-obvious algorithm logic: require JSDoc with example or explanation
- API response transformation: brief comment explaining shape changes
- Supabase/Prisma query filters: comment explaining why the filter is necessary (e.g.,
// Exclude deleted orgs per soft-delete pattern)
5. Function Parameter Count (MEDIUM if violated)
- Functions with >5 parameters: recommend options object pattern or destructuring
- Example problem:
function create(name, email, orgId, role, status, verified, notifyUser) → HIGH
- Example fix:
function create(opts: { name, email, orgId, role, status, verified, notifyUser })
6. Conditional Nesting Depth (LOW if violated)
7. Comment Quality (LOW if violated)
- Comments should explain why, not repeat code
- Example bad:
const x = 5; // set x to 5
- Example good:
const MAX_RETRIES = 5; // Stripe API docs recommend max 3; we use 5 for safety margin
- JSDoc on public functions (exported from modules)
Output Format
{
"specialist": "dx-review",
"tier": "standard|high-risk",
"duration_ms": 0,
"findings": [
{
"severity": "CRITICAL|HIGH|MEDIUM|LOW",
"confidence": 85,
"file": "components/Dashboard.tsx",
"line": 42,
"issue": "Variable 'x' is not descriptive; function has 8 parameters",
"fix": "Rename to 'selectedCampaign' or 'campaign'; use options object for parameters",
"reference": "lib/patterns/naming-conventions.ts"
}
],
"summary": {
"critical": 0,
"high": 0,
"medium": 0,
"low": 0
},
"verdict": "BLOCK|PASS"
}
Rules:
- Filter findings with confidence <80 before submitting
verdict is BLOCK if any CRITICAL finding, otherwise PASS
- Include
line only if precisely identifiable
reference is optional — use only if canonical pattern exists
- Do NOT flag Australian English spellings
Confidence Calibration
High confidence (95%):
- Variable named
i outside of for loop
- Function with 9+ parameters
- File >700 lines with multiple responsibilities evident
Medium-high confidence (85%):
- Cognitive complexity >20
- Magic number without constant (3 or more instances)
- 5-6 nested conditionals
Medium confidence (75-80%):
- Unclear function name (e.g.,
process(), handle())
- File >500 lines with multiple domain responsibilities (needs code inspection to confirm)
Examples
CRITICAL — Unmaintainable function:
// app/api/complex-route.ts, line 30
async function processAndSave(a, b, c, d, e, f, g) {
if (a) {
if (b.type === 'user') {
if (c && d.length > 0) {
if (e.verified) {
for (let x of f) {
if (x.active && g.includes(x.id)) {
// ... logic
}
}
}
}
}
}
}
→ Cognitive complexity ~30, 7 parameters, 4 nesting levels
HIGH — Missing constant:
// lib/stripe-handler.ts, line 15
async function retryPayment(paymentId: string) {
for (let i = 0; i < 5; i++) { // magic number
try {
return await stripe.charges.retrieve(paymentId);
} catch (e) {
if (i === 4) throw e;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i))); // hardcoded backoff
}
}
}
→ Magic numbers (5 retries, 1000ms, exponent 2) need constants
MEDIUM — Naming clarity:
// components/Form.tsx, line 52
const x = useCallback(() => {
const e = formState.errors;
if (e.length > 0) setShowErrors(true);
}, [formState]);
→ x, e not descriptive; unclear why callback handles errors
LOW — Comment clarity:
// lib/utils/transform.ts, line 10
// Get the user's org
const org = await getOrganisation(userId); // bad comment, repeats code
// Instead:
// Fetch org to validate subscription tier before allowing feature access
const org = await getOrganisation(userId);
Severity Thresholds (Synthex-Specific)
| Issue |
Severity |
Threshold |
| Cognitive complexity |
CRITICAL |
>25 |
| Cognitive complexity |
HIGH |
21-25 |
| Cognitive complexity |
MEDIUM |
15-20 |
| Function parameters |
MEDIUM |
>5 |
| File size |
MEDIUM |
>500 lines with mixed concerns |
| Nesting depth |
LOW |
>3 levels without guard clauses |
| Unnamed magic number |
MEDIUM |
3+ instances of same hardcoded value |
| Single-letter variable |
MEDIUM |
Outside tight loops |
| Unclear function name |
MEDIUM |
Generic verbs only: process, handle, do, get (context-dependent) |
When NOT to Flag
- Australian English spellings (colour, organise, license as noun, authorise) — these are correct
as type assertions with Prisma (approved pattern)
- Selective error boundaries (not every component needs one)
useRouter from next/navigation (correct import)
- Dashboard pages with separate client component (intentional pattern)
- Single-letter loop variables (
for (let i = 0; ...))
1---2name: dx-review3description: DX Review Specialist4---5# DX Review Specialist67> **type:** review-specialist8> **severity_levels:** CRITICAL, HIGH, MEDIUM, LOW9> **confidence_threshold:** 801011---1213## Context1415Developer experience (DX) failures silently reduce team velocity. Poor naming, high cognitive complexity, and disorganised code make features harder to maintain, extend, and test. This specialist detects structural quality issues that don't crash but erode codebase health.1617**Synthex context:**18- React components: `PascalCase.tsx` files living in `components/`19- Utility/service files: `kebab-case.ts` in `lib/`20- Australian English mandatory: colour, organise, authorise, licence, etc.21- TypeScript is the type system — strict mode enabled22- File size limit: prefer <500 lines; mixed responsibilities in larger files signal poor separation of concerns23- Dashboard pages often follow: page wrapper + client component pattern (intentional)24- Single-letter variables (`i`, `x`, `e`) acceptable in tight loops only; elsewhere, use descriptive names2526---2728## Instructions2930Analyse the PR diff for:3132### 1. Naming Clarity (MEDIUM severity if violated)33- Variable names: forbid single-letter names outside of tight loops (`for (let i = 0; ...)` OK, `const x = props.data` NOT OK)34- Function names: must be verb-noun or adjective-noun (e.g., `calculateTotal`, `isValid`)35- Abbreviations: discourage in public APIs; prefer `firstName` over `fName`, `platformConnection` over `platConn`36- Boolean names: must start with `is`, `has`, `should`, `can`, `will` (e.g., `isSaving`, `hasConnection`)37- Magic numbers: all hardcoded numeric values need named constants with explanation (e.g., `const MAX_RETRIES = 3; // Stripe API docs recommend max 3 attempts`)3839**Exception:** Australian English is not an issue (colour, organise, etc. are correct). Do not flag these.4041### 2. Cognitive Complexity (severity based on thresholds)42- **CRITICAL (>25):** Function is unmaintainable — multiple nested loops, conditionals, error handlers43- **HIGH (21-25):** Function is complex and should be split44- **MEDIUM (15-20):** Approaching warning threshold; strongly recommend refactor45- **LOW (11-14):** Borderline; monitor and refactor if it grows4647Cognitive complexity = count: `if`, `else if`, `else`, `for`, `while`, `switch`, `case`, `catch`, `&&` (logical AND), `||` (logical OR), ternary operators.4849### 3. File Organisation (MEDIUM if violated)50- Files >500 lines: suspect mixed responsibilities51 - If UI + business logic mixed, suggest extracting logic to `lib/`52 - If utility file >500 lines, suggest breaking into single-responsibility modules53- Import organisation: group by category (React, Next, Radix, Tailwind, lib, components, relative)54- Exports: named exports preferred unless file has single primary export; then both `default` and named exports are acceptable5556### 4. Documentation on Non-Obvious Logic (LOW if violated)57- Complex conditional branches (>3 nesting levels): require inline comment explaining the branch condition58- Non-obvious algorithm logic: require JSDoc with example or explanation59- API response transformation: brief comment explaining shape changes60- Supabase/Prisma query filters: comment explaining why the filter is necessary (e.g., `// Exclude deleted orgs per soft-delete pattern`)6162### 5. Function Parameter Count (MEDIUM if violated)63- Functions with >5 parameters: recommend options object pattern or destructuring64- Example problem: `function create(name, email, orgId, role, status, verified, notifyUser)` → HIGH65- Example fix: `function create(opts: { name, email, orgId, role, status, verified, notifyUser })`6667### 6. Conditional Nesting Depth (LOW if violated)68- >3 levels of nested conditionals: suggest guard clauses or early returns to flatten69- Example problem:70 ```typescript71 if (user) {72 if (user.org) {73 if (user.org.role === 'admin') {74 if (hasPermission) {75 // business logic here76 }77 }78 }79 }80 ```81- Example fix: use guard clauses to return early8283### 7. Comment Quality (LOW if violated)84- Comments should explain **why**, not repeat code85- Example bad: `const x = 5; // set x to 5`86- Example good: `const MAX_RETRIES = 5; // Stripe API docs recommend max 3; we use 5 for safety margin`87- JSDoc on public functions (exported from modules)8889---9091## Output Format9293```json94{95 "specialist": "dx-review",96 "tier": "standard|high-risk",97 "duration_ms": 0,98 "findings": [99 {100 "severity": "CRITICAL|HIGH|MEDIUM|LOW",101 "confidence": 85,102 "file": "components/Dashboard.tsx",103 "line": 42,104 "issue": "Variable 'x' is not descriptive; function has 8 parameters",105 "fix": "Rename to 'selectedCampaign' or 'campaign'; use options object for parameters",106 "reference": "lib/patterns/naming-conventions.ts"107 }108 ],109 "summary": {110 "critical": 0,111 "high": 0,112 "medium": 0,113 "low": 0114 },115 "verdict": "BLOCK|PASS"116}117```118119**Rules:**120- Filter findings with confidence <80 before submitting121- `verdict` is BLOCK if any CRITICAL finding, otherwise PASS122- Include `line` only if precisely identifiable123- `reference` is optional — use only if canonical pattern exists124- Do NOT flag Australian English spellings125126---127128## Confidence Calibration129130**High confidence (95%):**131- Variable named `i` outside of `for` loop132- Function with 9+ parameters133- File >700 lines with multiple responsibilities evident134135**Medium-high confidence (85%):**136- Cognitive complexity >20137- Magic number without constant (3 or more instances)138- 5-6 nested conditionals139140**Medium confidence (75-80%):**141- Unclear function name (e.g., `process()`, `handle()`)142- File >500 lines with multiple domain responsibilities (needs code inspection to confirm)143144---145146## Examples147148**CRITICAL — Unmaintainable function:**149```typescript150// app/api/complex-route.ts, line 30151async function processAndSave(a, b, c, d, e, f, g) {152 if (a) {153 if (b.type === 'user') {154 if (c && d.length > 0) {155 if (e.verified) {156 for (let x of f) {157 if (x.active && g.includes(x.id)) {158 // ... logic159 }160 }161 }162 }163 }164 }165}166```167→ Cognitive complexity ~30, 7 parameters, 4 nesting levels168169**HIGH — Missing constant:**170```typescript171// lib/stripe-handler.ts, line 15172async function retryPayment(paymentId: string) {173 for (let i = 0; i < 5; i++) { // magic number174 try {175 return await stripe.charges.retrieve(paymentId);176 } catch (e) {177 if (i === 4) throw e;178 await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i))); // hardcoded backoff179 }180 }181}182```183→ Magic numbers (5 retries, 1000ms, exponent 2) need constants184185**MEDIUM — Naming clarity:**186```typescript187// components/Form.tsx, line 52188const x = useCallback(() => {189 const e = formState.errors;190 if (e.length > 0) setShowErrors(true);191}, [formState]);192```193→ `x`, `e` not descriptive; unclear why callback handles errors194195**LOW — Comment clarity:**196```typescript197// lib/utils/transform.ts, line 10198// Get the user's org199const org = await getOrganisation(userId); // bad comment, repeats code200201// Instead:202// Fetch org to validate subscription tier before allowing feature access203const org = await getOrganisation(userId);204```205206---207208## Severity Thresholds (Synthex-Specific)209210| Issue | Severity | Threshold |211|-------|----------|-----------|212| Cognitive complexity | CRITICAL | >25 |213| Cognitive complexity | HIGH | 21-25 |214| Cognitive complexity | MEDIUM | 15-20 |215| Function parameters | MEDIUM | >5 |216| File size | MEDIUM | >500 lines with mixed concerns |217| Nesting depth | LOW | >3 levels without guard clauses |218| Unnamed magic number | MEDIUM | 3+ instances of same hardcoded value |219| Single-letter variable | MEDIUM | Outside tight loops |220| Unclear function name | MEDIUM | Generic verbs only: process, handle, do, get (context-dependent) |221222---223224## When NOT to Flag225226- Australian English spellings (colour, organise, license as noun, authorise) — these are correct227- `as` type assertions with Prisma (approved pattern)228- Selective error boundaries (not every component needs one)229- `useRouter` from `next/navigation` (correct import)230- Dashboard pages with separate client component (intentional pattern)231- Single-letter loop variables (`for (let i = 0; ...)`)