cursor-lint 0.2.0 → 0.3.1
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/README.md +5 -1
- package/package.json +2 -2
- package/src/cli.js +46 -2
- package/src/init.js +227 -0
package/README.md
CHANGED
|
@@ -81,10 +81,14 @@ cursor-lint --version Show version
|
|
|
81
81
|
|
|
82
82
|
Every check in cursor-lint comes from [actual experiments](https://dev.to/nedcodes) testing what Cursor does and doesn't follow. Not guesswork — data.
|
|
83
83
|
|
|
84
|
+
## Need a deeper review?
|
|
85
|
+
|
|
86
|
+
cursor-lint catches structural issues. For a full review of your rules, project structure, and model settings, I offer [$50 async setup audits](https://cursorrulespacks.gumroad.com/l/cursor-setup-audit). You get a written report with specific fixes, not generic advice.
|
|
87
|
+
|
|
84
88
|
## License
|
|
85
89
|
|
|
86
90
|
MIT
|
|
87
91
|
|
|
88
92
|
---
|
|
89
93
|
|
|
90
|
-
Made by [nedcodes](https://dev.to/nedcodes)
|
|
94
|
+
Made by [nedcodes](https://dev.to/nedcodes) · [Free rules collection](https://github.com/cursorrulespacks/cursorrules-collection) · [Setup audits](https://cursorrulespacks.gumroad.com/l/cursor-setup-audit)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-lint",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Lint your Cursor rules
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Lint your Cursor rules — catch common mistakes before they break your workflow",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cursor-lint": "src/cli.js"
|
package/src/cli.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { lintProject } = require('./index');
|
|
5
5
|
const { verifyProject } = require('./verify');
|
|
6
|
+
const { initProject } = require('./init');
|
|
6
7
|
|
|
7
|
-
const VERSION = '0.
|
|
8
|
+
const VERSION = '0.3.1';
|
|
8
9
|
|
|
9
10
|
const RED = '\x1b[31m';
|
|
10
11
|
const YELLOW = '\x1b[33m';
|
|
@@ -26,6 +27,7 @@ ${YELLOW}Options:${RESET}
|
|
|
26
27
|
--help, -h Show this help message
|
|
27
28
|
--version, -v Show version number
|
|
28
29
|
--verify Check if code follows rules with verify: blocks
|
|
30
|
+
--init Generate starter .mdc rules (auto-detects your stack)
|
|
29
31
|
|
|
30
32
|
${YELLOW}What it checks (default):${RESET}
|
|
31
33
|
• .cursorrules files (warns about agent mode compatibility)
|
|
@@ -56,6 +58,7 @@ ${YELLOW}verify: block syntax in .mdc frontmatter:${RESET}
|
|
|
56
58
|
${YELLOW}Examples:${RESET}
|
|
57
59
|
npx cursor-lint # Lint rule files
|
|
58
60
|
npx cursor-lint --verify # Check code against rules
|
|
61
|
+
npx cursor-lint --init # Generate starter rules for your project
|
|
59
62
|
|
|
60
63
|
${YELLOW}More info:${RESET}
|
|
61
64
|
https://github.com/cursorrulespacks/cursor-lint
|
|
@@ -77,8 +80,44 @@ async function main() {
|
|
|
77
80
|
|
|
78
81
|
const cwd = process.cwd();
|
|
79
82
|
const isVerify = args.includes('--verify');
|
|
83
|
+
const isInit = args.includes('--init');
|
|
80
84
|
|
|
81
|
-
if (
|
|
85
|
+
if (isInit) {
|
|
86
|
+
console.log(`\n🔍 cursor-lint v${VERSION} --init\n`);
|
|
87
|
+
console.log(`Detecting stack in ${cwd}...\n`);
|
|
88
|
+
|
|
89
|
+
const results = await initProject(cwd);
|
|
90
|
+
|
|
91
|
+
const stacks = Object.entries(results.detected)
|
|
92
|
+
.filter(([_, v]) => v)
|
|
93
|
+
.map(([k]) => k.charAt(0).toUpperCase() + k.slice(1));
|
|
94
|
+
|
|
95
|
+
if (stacks.length > 0) {
|
|
96
|
+
console.log(`Detected: ${stacks.join(', ')}\n`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (results.created.length > 0) {
|
|
100
|
+
console.log(`${GREEN}Created:${RESET}`);
|
|
101
|
+
for (const f of results.created) {
|
|
102
|
+
console.log(` ${GREEN}✓${RESET} .cursor/rules/${f}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (results.skipped.length > 0) {
|
|
107
|
+
console.log(`\n${YELLOW}Skipped (already exist):${RESET}`);
|
|
108
|
+
for (const f of results.skipped) {
|
|
109
|
+
console.log(` ${YELLOW}⚠${RESET} .cursor/rules/${f}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (results.created.length > 0) {
|
|
114
|
+
console.log(`\n${DIM}Run cursor-lint to check these rules${RESET}`);
|
|
115
|
+
console.log(`${DIM}Run cursor-lint --verify to check code against them${RESET}\n`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
process.exit(0);
|
|
119
|
+
|
|
120
|
+
} else if (isVerify) {
|
|
82
121
|
console.log(`\n🔍 cursor-lint v${VERSION} --verify\n`);
|
|
83
122
|
console.log(`Scanning ${cwd} for rule violations...\n`);
|
|
84
123
|
|
|
@@ -167,6 +206,11 @@ async function main() {
|
|
|
167
206
|
if (totalPassed > 0) parts.push(`${GREEN}${totalPassed} passed${RESET}`);
|
|
168
207
|
console.log(parts.join(', ') + '\n');
|
|
169
208
|
|
|
209
|
+
if (totalErrors > 0) {
|
|
210
|
+
console.log(`${DIM}Need help fixing these? Get a full setup review:${RESET}`);
|
|
211
|
+
console.log(`${CYAN}https://cursorrulespacks.gumroad.com/l/cursor-setup-audit${RESET}\n`);
|
|
212
|
+
}
|
|
213
|
+
|
|
170
214
|
process.exit(totalErrors > 0 ? 1 : 0);
|
|
171
215
|
}
|
|
172
216
|
}
|
package/src/init.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
async function initProject(projectPath) {
|
|
5
|
+
const detected = detectStack(projectPath);
|
|
6
|
+
const created = [];
|
|
7
|
+
const skipped = [];
|
|
8
|
+
|
|
9
|
+
const rulesDir = path.join(projectPath, '.cursor', 'rules');
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(rulesDir)) {
|
|
12
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const generalResult = writeRule(rulesDir, 'general.mdc', generateGeneral());
|
|
16
|
+
if (generalResult.created) created.push(generalResult.file);
|
|
17
|
+
else skipped.push(generalResult.file);
|
|
18
|
+
|
|
19
|
+
if (detected.typescript) {
|
|
20
|
+
const result = writeRule(rulesDir, 'typescript.mdc', generateTypeScript());
|
|
21
|
+
if (result.created) created.push(result.file);
|
|
22
|
+
else skipped.push(result.file);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (detected.react && !detected.nextjs) {
|
|
26
|
+
const result = writeRule(rulesDir, 'react.mdc', generateReact());
|
|
27
|
+
if (result.created) created.push(result.file);
|
|
28
|
+
else skipped.push(result.file);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (detected.nextjs) {
|
|
32
|
+
const result = writeRule(rulesDir, 'nextjs.mdc', generateNextJs());
|
|
33
|
+
if (result.created) created.push(result.file);
|
|
34
|
+
else skipped.push(result.file);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (detected.express) {
|
|
38
|
+
const result = writeRule(rulesDir, 'express.mdc', generateExpress());
|
|
39
|
+
if (result.created) created.push(result.file);
|
|
40
|
+
else skipped.push(result.file);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (detected.python) {
|
|
44
|
+
const result = writeRule(rulesDir, 'python.mdc', generatePython());
|
|
45
|
+
if (result.created) created.push(result.file);
|
|
46
|
+
else skipped.push(result.file);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { created, skipped, detected };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function detectStack(projectPath) {
|
|
53
|
+
const detected = {
|
|
54
|
+
typescript: false,
|
|
55
|
+
react: false,
|
|
56
|
+
nextjs: false,
|
|
57
|
+
express: false,
|
|
58
|
+
python: false,
|
|
59
|
+
node: false
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (fs.existsSync(path.join(projectPath, 'tsconfig.json'))) {
|
|
63
|
+
detected.typescript = true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
67
|
+
if (fs.existsSync(pkgPath)) {
|
|
68
|
+
detected.node = true;
|
|
69
|
+
try {
|
|
70
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
71
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
72
|
+
|
|
73
|
+
if (allDeps.react || allDeps['react-dom']) detected.react = true;
|
|
74
|
+
if (allDeps.next) { detected.nextjs = true; detected.react = true; }
|
|
75
|
+
if (allDeps.express) detected.express = true;
|
|
76
|
+
if (allDeps.typescript || allDeps['@types/node']) detected.typescript = true;
|
|
77
|
+
} catch (e) {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const files = fs.readdirSync(projectPath);
|
|
82
|
+
if (files.some(f => f.endsWith('.py')) ||
|
|
83
|
+
fs.existsSync(path.join(projectPath, 'requirements.txt')) ||
|
|
84
|
+
fs.existsSync(path.join(projectPath, 'pyproject.toml'))) {
|
|
85
|
+
detected.python = true;
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {}
|
|
88
|
+
|
|
89
|
+
return detected;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function writeRule(rulesDir, filename, content) {
|
|
93
|
+
const filePath = path.join(rulesDir, filename);
|
|
94
|
+
if (fs.existsSync(filePath)) return { file: filename, created: false };
|
|
95
|
+
fs.writeFileSync(filePath, content);
|
|
96
|
+
return { file: filename, created: true };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function generateGeneral() {
|
|
100
|
+
return `---
|
|
101
|
+
description: General code quality rules
|
|
102
|
+
alwaysApply: true
|
|
103
|
+
globs: ["*"]
|
|
104
|
+
verify:
|
|
105
|
+
- antipattern: "TODO"
|
|
106
|
+
message: "Resolve TODO comments before committing"
|
|
107
|
+
- antipattern: "FIXME"
|
|
108
|
+
message: "Resolve FIXME comments before committing"
|
|
109
|
+
- antipattern: "console\\\\.log"
|
|
110
|
+
message: "Remove console.log statements"
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
# General Guidelines
|
|
114
|
+
|
|
115
|
+
- Write clear, self-documenting code
|
|
116
|
+
- Use meaningful variable and function names
|
|
117
|
+
- Keep functions small and focused
|
|
118
|
+
- Remove all TODOs and FIXMEs before committing
|
|
119
|
+
- No console.log in production code
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function generateTypeScript() {
|
|
124
|
+
return `---
|
|
125
|
+
description: TypeScript best practices
|
|
126
|
+
alwaysApply: true
|
|
127
|
+
globs: ["*.ts", "*.tsx"]
|
|
128
|
+
verify:
|
|
129
|
+
- antipattern: ": any"
|
|
130
|
+
message: "Avoid using 'any' type - use proper typing"
|
|
131
|
+
- antipattern: "@ts-ignore"
|
|
132
|
+
message: "Remove @ts-ignore - fix the type error instead"
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
# TypeScript Rules
|
|
136
|
+
|
|
137
|
+
- Use strict TypeScript configuration
|
|
138
|
+
- Avoid \`any\` type - use \`unknown\` if type is truly unknown
|
|
139
|
+
- Use type inference where possible, explicit types where helpful
|
|
140
|
+
- Prefer interfaces for object shapes, types for unions/intersections
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function generateReact() {
|
|
145
|
+
return `---
|
|
146
|
+
description: React best practices
|
|
147
|
+
alwaysApply: true
|
|
148
|
+
globs: ["*.tsx", "*.jsx"]
|
|
149
|
+
verify:
|
|
150
|
+
- antipattern: "dangerouslySetInnerHTML"
|
|
151
|
+
message: "Avoid dangerouslySetInnerHTML - use proper sanitization if needed"
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
# React Rules
|
|
155
|
+
|
|
156
|
+
- Use functional components with hooks
|
|
157
|
+
- Before writing a useEffect, ask: can this be computed during render?
|
|
158
|
+
- Keep components small and focused
|
|
159
|
+
- Use proper key props in lists (never use array index as key for dynamic lists)
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function generateNextJs() {
|
|
164
|
+
return `---
|
|
165
|
+
description: Next.js App Router best practices
|
|
166
|
+
alwaysApply: true
|
|
167
|
+
globs: ["*.ts", "*.tsx"]
|
|
168
|
+
verify:
|
|
169
|
+
- antipattern: "getServerSideProps"
|
|
170
|
+
message: "Use App Router patterns instead of getServerSideProps"
|
|
171
|
+
- antipattern: "getStaticProps"
|
|
172
|
+
message: "Use App Router patterns instead of getStaticProps"
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
# Next.js Rules
|
|
176
|
+
|
|
177
|
+
- Use App Router (app directory), not Pages Router
|
|
178
|
+
- Mark components as 'use client' only when they need client-side interactivity
|
|
179
|
+
- Default to Server Components
|
|
180
|
+
- Use Server Actions for mutations instead of API routes
|
|
181
|
+
- Use the @/ path alias for imports
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function generateExpress() {
|
|
186
|
+
return `---
|
|
187
|
+
description: Express/Node.js best practices
|
|
188
|
+
alwaysApply: true
|
|
189
|
+
globs: ["*.js", "*.ts"]
|
|
190
|
+
verify:
|
|
191
|
+
- antipattern: "app\\\\.use\\\\(express\\\\.json\\\\(\\\\)\\\\)"
|
|
192
|
+
message: "Consider adding body size limits to express.json()"
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
# Express Rules
|
|
196
|
+
|
|
197
|
+
- Use async/await with proper error handling
|
|
198
|
+
- Always validate and sanitize user input
|
|
199
|
+
- Use middleware for cross-cutting concerns
|
|
200
|
+
- Add rate limiting for public endpoints
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function generatePython() {
|
|
205
|
+
return `---
|
|
206
|
+
description: Python best practices
|
|
207
|
+
alwaysApply: true
|
|
208
|
+
globs: ["*.py"]
|
|
209
|
+
verify:
|
|
210
|
+
- antipattern: "print\\\\("
|
|
211
|
+
message: "Use logging instead of print statements"
|
|
212
|
+
- antipattern: "import \\\\*"
|
|
213
|
+
message: "Avoid wildcard imports - import specific names"
|
|
214
|
+
- antipattern: "except:"
|
|
215
|
+
message: "Avoid bare except - catch specific exceptions"
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
# Python Rules
|
|
219
|
+
|
|
220
|
+
- Follow PEP 8 style guidelines
|
|
221
|
+
- Use type hints for function signatures
|
|
222
|
+
- Use logging instead of print statements
|
|
223
|
+
- Handle exceptions specifically, never use bare except
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = { initProject, detectStack };
|