spaghetti-slicer
v0.1.1
Published
Frontend best practices auditor for React/TypeScript codebases
Readme
🍝 spaghetti-slicer
A highly opinionated frontend best practices auditor for React & TypeScript codebases. It is designed to automatically scan, analyze, and slice away "spaghetti code" before it reaches production—especially code generated by AI coding assistants like Cursor, v0, Bolt, and Lovable.
✨ Features
- ⚡ Zero Config: Works out of the box with zero setup. Point it at your code and let it slice.
- 📊 Quantifiable Scores: Generates a weighted score out of 100 across 5 core categories, grading your repository from Excellent to Poor.
- 🛠️ Built for AI-Generated Code: Catches common anti-patterns that LLMs frequently introduce (such as state bloat, inline fetching, and missing dimensions).
- 🤖 CI/CD Friendly: Fail pipelines automatically with
--min-scorewhen code quality falls below your team's threshold. - 📋 Flexible Output: Print beautiful console summaries with
oraspinners andboxenlayouts, or stream raw JSON to stdout using--json.
🚀 Installation
Run instantly with npx (No Install)
npx spaghetti-slicer ./srcInstall Globally
npm install -g spaghetti-slicer
# Run audit
spaghetti-slicer ./src🛠️ Usage & CLI Flags
# Audit a whole folder or specific files
spaghetti-slicer ./src
spaghetti-slicer ./src/components/Button.tsx
# Exit with code 1 if score drops below 80 (perfect for CI checks)
spaghetti-slicer ./src --min-score=80
# Filter rules to run only a specific category
spaghetti-slicer ./src --rule=architecture
spaghetti-slicer ./src --rule=react
# Print clean JSON for external scripting
spaghetti-slicer ./src --json
# Show interactive lint/eslint autofix recommendations
spaghetti-slicer ./src --fix🔍 Code Quality Rules
spaghetti-slicer runs rules in several core categories:
🏛️ Architecture (Weight: 25%)
| Rule ID | Severity | Description |
|:---|:---:|:---|
| component-length | 🔴 Critical | Components exceeding 200 lines should be broken up |
| business-logic-in-jsx | 🟡 Warning | Inline data operations (.filter(), .reduce(), .sort()) in JSX |
| direct-fetch-in-component | 🔴 Critical | Making raw HTTP fetch/axios requests directly inside component bodies |
| state-bloat | 🟡 Warning | Defining more than 5 useState hooks in a single component |
| hardcoded-secrets-endpoints | 🔴 Critical | Hardcoded raw URL strings or potential API credentials/keys |
⚛️ React (Weight: 20%)
| Rule ID | Severity | Description |
|:---|:---:|:---|
| index-as-key | 🔴 Critical | Array iteration index used as the React key prop |
| missing-error-boundary | 🟡 Warning | Codebase lacks any React Error Boundary components |
| no-sub-renders | 🟡 Warning | Helper render functions (e.g. renderHeader()) nested in components |
| fat-controller | 🟡 Warning | Non-JSX javascript logic exceeds 75% of a component's total line length |
⚡ Performance (Weight: 15%)
| Rule ID | Severity | Description |
|:---|:---:|:---|
| image-missing-dimensions | 🟡 Warning | <img> or <Image> tags missing width or height attributes (causes CLS) |
💡 Code Patterns: Bad vs. Good
Here's how spaghetti-slicer helps you clean up common code smells:
1. Direct Fetch in Component (direct-fetch-in-component)
- ❌ Bad: Making raw API calls inside a render or useEffect lifecycle.
export function UserProfile() { useEffect(() => { fetch('/api/user/profile').then(res => res.json()).then(data => {...}); }, []); return <div>User Profile</div>; } - Good: Extract to a custom hook or API service layer.
import { useUserProfile } from '../hooks/useUserProfile';
export function UserProfile() {
const { profile, loading } = useUserProfile();
return <div>User Profile</div>;
}2. State Bloat (state-bloat)
- ❌ Bad: Managing component states using too many individual hooks.
const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [age, setAge] = useState(0); const [address, setAddress] = useState(''); const [phone, setPhone] = useState(''); const [company, setCompany] = useState(''); // ⚠️ State Bloat Triggered (6 hooks) - Good: Merge related states or delegate to
useReducer/ state containers.
const [formData, setFormData] = useState({
name: '', email: '', age: 0, address: '', phone: '', company: ''
});3. Nested Helper Renders (no-sub-renders)
- ❌ Bad: Defining sub-rendering helper functions inside the main component body.
export function ProductCard({ title, price }) { const renderBadge = () => <span className="badge">New</span>; // ⚠️ Defined inside Card return ( <div> <h3>{title}</h3> {renderBadge()} </div> ); } - Good: Extract to its own component.
function Badge() {
return <span className="badge">New</span>;
}
export function ProductCard({ title, price }) {
return (
<div>
<h3>{title}</h3>
<Badge />
</div>
);
}📈 Scoring System
Your project starts with a score of 100 for each category. Points are deducted per violation:
- 🔴 Critical Violation:
-10 points - 🟡 Warning Violation:
-5 points - 🔵 Info Violation:
-2 points
A total score is calculated using the weighted averages of all categories. Your repository is then assigned a Grade:
| Score | Grade | Description | |:---:|:---:|:---| | 90 – 100 | ✅ Excellent | Clean codebase, minimal code smells | | 75 – 89 | ✅ Good | Solid structure, minor refactoring recommended | | 60 – 74 | ⚠️ Needs Work | Code smells present, potential architectural issues | | < 60 | ❌ Poor | High risk of bugs and difficult maintenance |
🤖 GitHub Actions CI/CD Integration
Catch regressions automatically on every Pull Request. Add this workflow as .github/workflows/spaghetti-slicer.yml:
name: Code Quality Audit
on: [pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Build Project
run: npm run build
- name: Run Spaghetti Slicer
# Exit and fail the action if quality score falls below 80%
run: npx spaghetti-slicer ./src --min-score=80⚙️ Local Development & Contributing
We welcome community contributions to add more rules or improve CLI performance!
Prerequisites & Setup
- Fork and clone the repository.
- Install dependencies:
npm install
Running Tests
We use vitest for fast static-analysis rule validation:
# Run tests once
npm run test:run
# Run tests in watch mode
npm run testLinking the CLI locally
Test your local changes against any separate project directory on your computer:
# 1. From the spaghetti-slicer repository directory
npm run build
npm link
# 2. Run globally linked CLI in another project directory
spaghetti-slicer /path/to/other-project/srcAdding a New Rule
- Create your rule under
src/rules/<category>/<rule-name>.tsimplementing theRuleinterface. - Register the rule in
src/rules/index.ts. - Write test cases covering pass and fail fixtures under
tests/rules/<category>/<rule-name>.test.ts. - Run
npm run test:runto confirm everything works!
📄 License
This project is open-source and licensed under the MIT License.
