cursor-lint 0.10.0 → 0.11.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 +4 -4
- package/package.json +5 -4
- package/src/cli.js +6 -5
- package/src/generate.js +1 -1
- package/src/index.js +100 -1
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ Every check in cursor-lint comes from [actual experiments](https://dev.to/nedcod
|
|
|
121
121
|
|
|
122
122
|
## Need a deeper review?
|
|
123
123
|
|
|
124
|
-
cursor-lint catches structural issues. For a full review of your rules, project structure, and model settings, I offer [$50 async setup audits](https://
|
|
124
|
+
cursor-lint catches structural issues. For a full review of your rules, project structure, and model settings, I offer [$50 async setup audits](https://nedcodes.gumroad.com/l/cursor-setup-audit). You get a written report with specific fixes, not generic advice.
|
|
125
125
|
|
|
126
126
|
## License
|
|
127
127
|
|
|
@@ -129,12 +129,12 @@ MIT
|
|
|
129
129
|
|
|
130
130
|
---
|
|
131
131
|
|
|
132
|
-
Made by [nedcodes](https://dev.to/nedcodes) · [Free rules collection](https://github.com/
|
|
132
|
+
Made by [nedcodes](https://dev.to/nedcodes) · [Free rules collection](https://github.com/nedcodes-ok/cursorrules-collection) · [Setup audits](https://nedcodes.gumroad.com/l/cursor-setup-audit)
|
|
133
133
|
|
|
134
134
|
---
|
|
135
135
|
|
|
136
136
|
## Related
|
|
137
137
|
|
|
138
|
-
- [cursorrules-collection](https://github.com/
|
|
139
|
-
- [Cursor Setup Audit](https://
|
|
138
|
+
- [cursorrules-collection](https://github.com/nedcodes-ok/cursorrules-collection) — 104 free .mdc rules
|
|
139
|
+
- [Cursor Setup Audit](https://nedcodes.gumroad.com/l/cursor-setup-audit) — Professional review of your rules setup ($50)
|
|
140
140
|
- [Articles on Dev.to](https://dev.to/nedcodes) — Guides on writing effective Cursor rules
|
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.11.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"
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
],
|
|
22
22
|
"author": "nedcodes",
|
|
23
23
|
"license": "MIT",
|
|
24
|
+
"homepage": "https://github.com/nedcodes-ok/cursor-lint",
|
|
24
25
|
"repository": {
|
|
25
26
|
"type": "git",
|
|
26
|
-
"url": "https://github.com/
|
|
27
|
+
"url": "https://github.com/nedcodes-ok/cursor-lint.git"
|
|
27
28
|
},
|
|
28
29
|
"engines": {
|
|
29
30
|
"node": ">=16"
|
|
@@ -31,4 +32,4 @@
|
|
|
31
32
|
"files": [
|
|
32
33
|
"src/"
|
|
33
34
|
]
|
|
34
|
-
}
|
|
35
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ const { fixProject } = require('./fix');
|
|
|
8
8
|
const { generateRules, suggestSkills } = require('./generate');
|
|
9
9
|
const { checkVersions, checkRuleVersionMismatches } = require('./versions');
|
|
10
10
|
|
|
11
|
-
const VERSION = '0.
|
|
11
|
+
const VERSION = '0.11.0';
|
|
12
12
|
|
|
13
13
|
const RED = '\x1b[31m';
|
|
14
14
|
const YELLOW = '\x1b[33m';
|
|
@@ -39,6 +39,7 @@ ${YELLOW}Options:${RESET}
|
|
|
39
39
|
${YELLOW}What it checks (default):${RESET}
|
|
40
40
|
• .cursorrules files (warns about agent mode compatibility)
|
|
41
41
|
• .cursor/rules/*.mdc files (frontmatter, alwaysApply, etc.)
|
|
42
|
+
• Agent skill files (SKILL.md in .claude/skills/, skills/)
|
|
42
43
|
• Vague rules that won't change AI behavior
|
|
43
44
|
• YAML syntax errors
|
|
44
45
|
|
|
@@ -69,7 +70,7 @@ ${YELLOW}Examples:${RESET}
|
|
|
69
70
|
npx cursor-lint --generate # Download community rules for your stack
|
|
70
71
|
|
|
71
72
|
${YELLOW}More info:${RESET}
|
|
72
|
-
https://github.com/
|
|
73
|
+
https://github.com/nedcodes-ok/cursor-lint
|
|
73
74
|
`);
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -431,10 +432,10 @@ async function main() {
|
|
|
431
432
|
console.log(parts.join(', ') + '\n');
|
|
432
433
|
|
|
433
434
|
if (totalErrors > 0) {
|
|
434
|
-
console.log(`${DIM}
|
|
435
|
-
console.log(`${CYAN}
|
|
435
|
+
console.log(`${DIM}Try ${CYAN}cursor-lint --fix${RESET}${DIM} to auto-repair frontmatter issues.${RESET}`);
|
|
436
|
+
console.log(`${DIM}Run ${CYAN}cursor-lint --order${RESET}${DIM} to check which rules are actually loading.${RESET}\n`);
|
|
436
437
|
} else if (totalPassed > 0) {
|
|
437
|
-
console.log(`${DIM}If cursor-lint saved you time: ${CYAN}https://github.com/
|
|
438
|
+
console.log(`${DIM}If cursor-lint saved you time: ${CYAN}https://github.com/nedcodes-ok/cursor-lint${RESET} ${DIM}(⭐ helps others find it)${RESET}\n`);
|
|
438
439
|
}
|
|
439
440
|
|
|
440
441
|
process.exit(totalErrors > 0 ? 1 : 0);
|
package/src/generate.js
CHANGED
|
@@ -2,7 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const https = require('https');
|
|
4
4
|
|
|
5
|
-
const BASE_URL = 'https://raw.githubusercontent.com/
|
|
5
|
+
const BASE_URL = 'https://raw.githubusercontent.com/nedcodes-ok/cursorrules-collection/main/rules-mdc/';
|
|
6
6
|
|
|
7
7
|
// package.json dependencies → rule files
|
|
8
8
|
const PKG_DEP_MAP = {
|
package/src/index.js
CHANGED
|
@@ -94,6 +94,98 @@ async function lintMdcFile(filePath) {
|
|
|
94
94
|
return { file: filePath, issues };
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
async function lintSkillFile(filePath) {
|
|
98
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
99
|
+
const issues = [];
|
|
100
|
+
|
|
101
|
+
const fm = parseFrontmatter(content);
|
|
102
|
+
|
|
103
|
+
if (!fm.found) {
|
|
104
|
+
issues.push({ severity: 'error', message: 'Missing YAML frontmatter', hint: 'Add --- block with name and description fields' });
|
|
105
|
+
} else if (fm.error) {
|
|
106
|
+
issues.push({ severity: 'error', message: `YAML frontmatter error: ${fm.error}`, hint: 'Fix frontmatter syntax' });
|
|
107
|
+
} else {
|
|
108
|
+
if (!fm.data.name) {
|
|
109
|
+
issues.push({ severity: 'error', message: 'Missing name in frontmatter', hint: 'Add name: your-skill-name to frontmatter' });
|
|
110
|
+
}
|
|
111
|
+
if (!fm.data.description) {
|
|
112
|
+
issues.push({ severity: 'error', message: 'Missing description in frontmatter', hint: 'Add a description so the agent knows when to use this skill' });
|
|
113
|
+
}
|
|
114
|
+
if (fm.data.description && fm.data.description.length < 20) {
|
|
115
|
+
issues.push({ severity: 'warning', message: 'Description is very short', hint: 'A longer description helps agents understand when to invoke this skill' });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check body content
|
|
120
|
+
const body = fm.found ? content.replace(/^---\n[\s\S]*?\n---\n?/, '') : content;
|
|
121
|
+
|
|
122
|
+
if (body.trim().length === 0) {
|
|
123
|
+
issues.push({ severity: 'error', message: 'Skill file has no body content', hint: 'Add instructions for the agent after the frontmatter' });
|
|
124
|
+
} else if (body.trim().length < 50) {
|
|
125
|
+
issues.push({ severity: 'warning', message: 'Skill body is very short (< 50 chars)', hint: 'Skills with more detail produce better agent behavior' });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for headings (structure)
|
|
129
|
+
const headings = body.match(/^#{1,3}\s+.+/gm);
|
|
130
|
+
if (body.trim().length > 500 && (!headings || headings.length === 0)) {
|
|
131
|
+
issues.push({ severity: 'warning', message: 'Long skill with no headings', hint: 'Add ## sections to organize instructions for better agent comprehension' });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Vague rules (same as .mdc)
|
|
135
|
+
const contentLower = content.toLowerCase();
|
|
136
|
+
for (const pattern of VAGUE_PATTERNS) {
|
|
137
|
+
const idx = contentLower.indexOf(pattern);
|
|
138
|
+
if (idx !== -1) {
|
|
139
|
+
const lineNum = content.slice(0, idx).split('\n').length;
|
|
140
|
+
issues.push({ severity: 'warning', message: `Vague instruction: "${pattern}"`, line: lineNum });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { file: filePath, issues };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function findSkillDirs(dir) {
|
|
148
|
+
const skillDirs = [];
|
|
149
|
+
// .claude/skills/ (Claude Code)
|
|
150
|
+
const claudeSkills = path.join(dir, '.claude', 'skills');
|
|
151
|
+
if (fs.existsSync(claudeSkills)) skillDirs.push(claudeSkills);
|
|
152
|
+
// .cursor/skills/ (Cursor agent skills - future)
|
|
153
|
+
const cursorSkills = path.join(dir, '.cursor', 'skills');
|
|
154
|
+
if (fs.existsSync(cursorSkills)) skillDirs.push(cursorSkills);
|
|
155
|
+
// skills/ at project root (skills.sh convention)
|
|
156
|
+
const rootSkills = path.join(dir, 'skills');
|
|
157
|
+
if (fs.existsSync(rootSkills) && fs.statSync(rootSkills).isDirectory()) {
|
|
158
|
+
// Only if it looks like agent skills (has SKILL.md files in subdirs)
|
|
159
|
+
try {
|
|
160
|
+
const entries = fs.readdirSync(rootSkills);
|
|
161
|
+
const hasSkillMd = entries.some(e => {
|
|
162
|
+
const sub = path.join(rootSkills, e);
|
|
163
|
+
return fs.statSync(sub).isDirectory() && fs.existsSync(path.join(sub, 'SKILL.md'));
|
|
164
|
+
});
|
|
165
|
+
if (hasSkillMd) skillDirs.push(rootSkills);
|
|
166
|
+
} catch {}
|
|
167
|
+
}
|
|
168
|
+
return skillDirs;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function collectSkillFiles(skillDirs) {
|
|
172
|
+
const files = [];
|
|
173
|
+
for (const dir of skillDirs) {
|
|
174
|
+
try {
|
|
175
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
176
|
+
const sub = path.join(dir, entry);
|
|
177
|
+
if (fs.statSync(sub).isDirectory()) {
|
|
178
|
+
const skillMd = path.join(sub, 'SKILL.md');
|
|
179
|
+
if (fs.existsSync(skillMd)) files.push(skillMd);
|
|
180
|
+
}
|
|
181
|
+
// Also handle flat SKILL.md files
|
|
182
|
+
if (entry === 'SKILL.md') files.push(path.join(dir, entry));
|
|
183
|
+
}
|
|
184
|
+
} catch {}
|
|
185
|
+
}
|
|
186
|
+
return files;
|
|
187
|
+
}
|
|
188
|
+
|
|
97
189
|
async function lintCursorrules(filePath) {
|
|
98
190
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
99
191
|
const issues = [];
|
|
@@ -134,10 +226,17 @@ async function lintProject(dir) {
|
|
|
134
226
|
}
|
|
135
227
|
}
|
|
136
228
|
|
|
229
|
+
// Skill files (.claude/skills/, skills/, etc.)
|
|
230
|
+
const skillDirs = findSkillDirs(dir);
|
|
231
|
+
const skillFiles = collectSkillFiles(skillDirs);
|
|
232
|
+
for (const sf of skillFiles) {
|
|
233
|
+
results.push(await lintSkillFile(sf));
|
|
234
|
+
}
|
|
235
|
+
|
|
137
236
|
if (results.length === 0) {
|
|
138
237
|
results.push({
|
|
139
238
|
file: dir,
|
|
140
|
-
issues: [{ severity: 'warning', message: 'No Cursor rules found in this directory' }],
|
|
239
|
+
issues: [{ severity: 'warning', message: 'No Cursor rules or agent skills found in this directory' }],
|
|
141
240
|
});
|
|
142
241
|
}
|
|
143
242
|
|