cursor-lint 0.3.0 → 0.4.0
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 +16 -1
- package/package.json +9 -4
- package/src/cli.js +44 -2
- package/src/fix.js +89 -0
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# cursor-lint
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/cursor-lint)
|
|
4
|
+
[](https://www.npmjs.com/package/cursor-lint)
|
|
5
|
+
|
|
3
6
|
Lint your [Cursor](https://cursor.com) rules. Catch common mistakes before they silently break your workflow.
|
|
4
7
|
|
|
5
8
|
```bash
|
|
@@ -81,10 +84,22 @@ cursor-lint --version Show version
|
|
|
81
84
|
|
|
82
85
|
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
86
|
|
|
87
|
+
## Need a deeper review?
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
84
91
|
## License
|
|
85
92
|
|
|
86
93
|
MIT
|
|
87
94
|
|
|
88
95
|
---
|
|
89
96
|
|
|
90
|
-
Made by [nedcodes](https://dev.to/nedcodes)
|
|
97
|
+
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)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Related
|
|
102
|
+
|
|
103
|
+
- [cursorrules-collection](https://github.com/cursorrulespacks/cursorrules-collection) — 77+ free .mdc rules
|
|
104
|
+
- [Cursor Setup Audit](https://cursorrulespacks.gumroad.com/l/cursor-setup-audit) — Professional review of your rules setup ($50)
|
|
105
|
+
- [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.4.0",
|
|
4
|
+
"description": "Lint your Cursor rules \u2014 catch common mistakes before they break your workflow",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cursor-lint": "src/cli.js"
|
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
"cursorrules",
|
|
13
13
|
"mdc",
|
|
14
14
|
"linter",
|
|
15
|
-
"developer-tools"
|
|
15
|
+
"developer-tools",
|
|
16
|
+
"cursor-rules",
|
|
17
|
+
"lint",
|
|
18
|
+
"validate",
|
|
19
|
+
"ai-coding",
|
|
20
|
+
"code-quality"
|
|
16
21
|
],
|
|
17
22
|
"author": "nedcodes",
|
|
18
23
|
"license": "MIT",
|
|
@@ -26,4 +31,4 @@
|
|
|
26
31
|
"files": [
|
|
27
32
|
"src/"
|
|
28
33
|
]
|
|
29
|
-
}
|
|
34
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -4,8 +4,9 @@ const path = require('path');
|
|
|
4
4
|
const { lintProject } = require('./index');
|
|
5
5
|
const { verifyProject } = require('./verify');
|
|
6
6
|
const { initProject } = require('./init');
|
|
7
|
+
const { fixProject } = require('./fix');
|
|
7
8
|
|
|
8
|
-
const VERSION = '0.
|
|
9
|
+
const VERSION = '0.4.0';
|
|
9
10
|
|
|
10
11
|
const RED = '\x1b[31m';
|
|
11
12
|
const YELLOW = '\x1b[33m';
|
|
@@ -28,6 +29,7 @@ ${YELLOW}Options:${RESET}
|
|
|
28
29
|
--version, -v Show version number
|
|
29
30
|
--verify Check if code follows rules with verify: blocks
|
|
30
31
|
--init Generate starter .mdc rules (auto-detects your stack)
|
|
32
|
+
--fix Auto-fix common issues (missing frontmatter, alwaysApply)
|
|
31
33
|
|
|
32
34
|
${YELLOW}What it checks (default):${RESET}
|
|
33
35
|
• .cursorrules files (warns about agent mode compatibility)
|
|
@@ -81,8 +83,43 @@ async function main() {
|
|
|
81
83
|
const cwd = process.cwd();
|
|
82
84
|
const isVerify = args.includes('--verify');
|
|
83
85
|
const isInit = args.includes('--init');
|
|
86
|
+
const isFix = args.includes('--fix');
|
|
84
87
|
|
|
85
|
-
if (
|
|
88
|
+
if (isFix) {
|
|
89
|
+
console.log(`\n🔧 cursor-lint v${VERSION} --fix\n`);
|
|
90
|
+
console.log(`Scanning ${cwd} for fixable issues...\n`);
|
|
91
|
+
|
|
92
|
+
const results = await fixProject(cwd);
|
|
93
|
+
|
|
94
|
+
if (results.length === 0) {
|
|
95
|
+
console.log(`${YELLOW}No .mdc files found in .cursor/rules/${RESET}\n`);
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let totalFixed = 0;
|
|
100
|
+
for (const result of results) {
|
|
101
|
+
const relPath = path.relative(cwd, result.file) || result.file;
|
|
102
|
+
if (result.changes.length > 0) {
|
|
103
|
+
console.log(`${GREEN}✓${RESET} ${relPath}`);
|
|
104
|
+
for (const change of result.changes) {
|
|
105
|
+
console.log(` ${DIM}→ ${change}${RESET}`);
|
|
106
|
+
}
|
|
107
|
+
totalFixed++;
|
|
108
|
+
} else {
|
|
109
|
+
console.log(`${DIM} ${relPath} — nothing to fix${RESET}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log();
|
|
114
|
+
console.log('─'.repeat(50));
|
|
115
|
+
if (totalFixed > 0) {
|
|
116
|
+
console.log(`${GREEN}Fixed ${totalFixed} file(s)${RESET}. Run cursor-lint to verify.\n`);
|
|
117
|
+
} else {
|
|
118
|
+
console.log(`${GREEN}All files look good — nothing to fix${RESET}\n`);
|
|
119
|
+
}
|
|
120
|
+
process.exit(0);
|
|
121
|
+
|
|
122
|
+
} else if (isInit) {
|
|
86
123
|
console.log(`\n🔍 cursor-lint v${VERSION} --init\n`);
|
|
87
124
|
console.log(`Detecting stack in ${cwd}...\n`);
|
|
88
125
|
|
|
@@ -206,6 +243,11 @@ async function main() {
|
|
|
206
243
|
if (totalPassed > 0) parts.push(`${GREEN}${totalPassed} passed${RESET}`);
|
|
207
244
|
console.log(parts.join(', ') + '\n');
|
|
208
245
|
|
|
246
|
+
if (totalErrors > 0) {
|
|
247
|
+
console.log(`${DIM}Need help fixing these? Get a full setup review:${RESET}`);
|
|
248
|
+
console.log(`${CYAN}https://cursorrulespacks.gumroad.com/l/cursor-setup-audit${RESET}\n`);
|
|
249
|
+
}
|
|
250
|
+
|
|
209
251
|
process.exit(totalErrors > 0 ? 1 : 0);
|
|
210
252
|
}
|
|
211
253
|
}
|
package/src/fix.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function addFrontmatter(content, filePath) {
|
|
5
|
+
const name = path.basename(filePath, '.mdc');
|
|
6
|
+
const frontmatter = `---
|
|
7
|
+
description: ${name} rules
|
|
8
|
+
alwaysApply: true
|
|
9
|
+
---
|
|
10
|
+
`;
|
|
11
|
+
return { fixed: frontmatter + content, changes: ['Added YAML frontmatter with alwaysApply: true'] };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function addAlwaysApply(content) {
|
|
15
|
+
// Insert alwaysApply: true after the first ---
|
|
16
|
+
const match = content.match(/^(---\n)([\s\S]*?)(---)/);
|
|
17
|
+
if (!match) return { fixed: content, changes: [] };
|
|
18
|
+
|
|
19
|
+
const frontmatterBody = match[2];
|
|
20
|
+
if (frontmatterBody.includes('alwaysApply')) return { fixed: content, changes: [] };
|
|
21
|
+
|
|
22
|
+
const newFm = match[1] + 'alwaysApply: true\n' + frontmatterBody + match[3];
|
|
23
|
+
const rest = content.slice(match[0].length);
|
|
24
|
+
return { fixed: newFm + rest, changes: ['Added alwaysApply: true to frontmatter'] };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function addDescription(content, filePath) {
|
|
28
|
+
const match = content.match(/^(---\n)([\s\S]*?)(---)/);
|
|
29
|
+
if (!match) return { fixed: content, changes: [] };
|
|
30
|
+
|
|
31
|
+
const frontmatterBody = match[2];
|
|
32
|
+
if (frontmatterBody.includes('description')) return { fixed: content, changes: [] };
|
|
33
|
+
|
|
34
|
+
const name = path.basename(filePath, '.mdc');
|
|
35
|
+
const newFm = match[1] + `description: ${name} rules\n` + frontmatterBody + match[3];
|
|
36
|
+
const rest = content.slice(match[0].length);
|
|
37
|
+
return { fixed: newFm + rest, changes: [`Added description: "${name} rules" to frontmatter`] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function fixFile(filePath) {
|
|
41
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
42
|
+
const allChanges = [];
|
|
43
|
+
|
|
44
|
+
// Check if frontmatter exists
|
|
45
|
+
const hasFm = content.match(/^---\n[\s\S]*?\n---/);
|
|
46
|
+
|
|
47
|
+
if (!hasFm) {
|
|
48
|
+
const result = addFrontmatter(content, filePath);
|
|
49
|
+
content = result.fixed;
|
|
50
|
+
allChanges.push(...result.changes);
|
|
51
|
+
} else {
|
|
52
|
+
// Fix missing alwaysApply
|
|
53
|
+
const r1 = addAlwaysApply(content);
|
|
54
|
+
if (r1.changes.length) {
|
|
55
|
+
content = r1.fixed;
|
|
56
|
+
allChanges.push(...r1.changes);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Fix missing description
|
|
60
|
+
const r2 = addDescription(content, filePath);
|
|
61
|
+
if (r2.changes.length) {
|
|
62
|
+
content = r2.fixed;
|
|
63
|
+
allChanges.push(...r2.changes);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (allChanges.length > 0) {
|
|
68
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { file: filePath, changes: allChanges };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function fixProject(dir) {
|
|
75
|
+
const results = [];
|
|
76
|
+
const rulesDir = path.join(dir, '.cursor', 'rules');
|
|
77
|
+
|
|
78
|
+
if (fs.existsSync(rulesDir) && fs.statSync(rulesDir).isDirectory()) {
|
|
79
|
+
for (const entry of fs.readdirSync(rulesDir)) {
|
|
80
|
+
if (entry.endsWith('.mdc')) {
|
|
81
|
+
results.push(await fixFile(path.join(rulesDir, entry)));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { fixProject, fixFile };
|