cursor-lint 0.4.0 → 0.5.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 CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  Lint your [Cursor](https://cursor.com) rules. Catch common mistakes before they silently break your workflow.
7
7
 
8
+ ![cursor-lint demo](demo.png)
9
+
8
10
  ```bash
9
11
  npx cursor-lint
10
12
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-lint",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Lint your Cursor rules \u2014 catch common mistakes before they break your workflow",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -5,8 +5,9 @@ const { lintProject } = require('./index');
5
5
  const { verifyProject } = require('./verify');
6
6
  const { initProject } = require('./init');
7
7
  const { fixProject } = require('./fix');
8
+ const { generateRules } = require('./generate');
8
9
 
9
- const VERSION = '0.4.0';
10
+ const VERSION = '0.5.0';
10
11
 
11
12
  const RED = '\x1b[31m';
12
13
  const YELLOW = '\x1b[33m';
@@ -30,6 +31,7 @@ ${YELLOW}Options:${RESET}
30
31
  --verify Check if code follows rules with verify: blocks
31
32
  --init Generate starter .mdc rules (auto-detects your stack)
32
33
  --fix Auto-fix common issues (missing frontmatter, alwaysApply)
34
+ --generate Auto-detect stack & download matching .mdc rules from GitHub
33
35
 
34
36
  ${YELLOW}What it checks (default):${RESET}
35
37
  • .cursorrules files (warns about agent mode compatibility)
@@ -61,6 +63,7 @@ ${YELLOW}Examples:${RESET}
61
63
  npx cursor-lint # Lint rule files
62
64
  npx cursor-lint --verify # Check code against rules
63
65
  npx cursor-lint --init # Generate starter rules for your project
66
+ npx cursor-lint --generate # Download community rules for your stack
64
67
 
65
68
  ${YELLOW}More info:${RESET}
66
69
  https://github.com/cursorrulespacks/cursor-lint
@@ -84,8 +87,50 @@ async function main() {
84
87
  const isVerify = args.includes('--verify');
85
88
  const isInit = args.includes('--init');
86
89
  const isFix = args.includes('--fix');
90
+ const isGenerate = args.includes('--generate');
87
91
 
88
- if (isFix) {
92
+ if (isGenerate) {
93
+ console.log(`\nšŸš€ cursor-lint v${VERSION} --generate\n`);
94
+ console.log(`Detecting stack in ${cwd}...\n`);
95
+
96
+ const results = await generateRules(cwd);
97
+
98
+ if (results.detected.length > 0) {
99
+ console.log(`${CYAN}Detected:${RESET} ${results.detected.join(', ')}\n`);
100
+ } else {
101
+ console.log(`${YELLOW}No recognized stack detected.${RESET}`);
102
+ console.log(`${DIM}Supports: package.json, tsconfig.json, requirements.txt, Cargo.toml, go.mod, Dockerfile${RESET}\n`);
103
+ process.exit(0);
104
+ }
105
+
106
+ if (results.created.length > 0) {
107
+ console.log(`${GREEN}Downloaded:${RESET}`);
108
+ for (const r of results.created) {
109
+ console.log(` ${GREEN}āœ“${RESET} .cursor/rules/${r.file} ${DIM}(${r.stack})${RESET}`);
110
+ }
111
+ }
112
+
113
+ if (results.skipped.length > 0) {
114
+ console.log(`\n${YELLOW}Skipped (already exist):${RESET}`);
115
+ for (const r of results.skipped) {
116
+ console.log(` ${YELLOW}⚠${RESET} .cursor/rules/${r.file}`);
117
+ }
118
+ }
119
+
120
+ if (results.failed.length > 0) {
121
+ console.log(`\n${RED}Failed:${RESET}`);
122
+ for (const r of results.failed) {
123
+ console.log(` ${RED}āœ—${RESET} ${r.file} — ${r.error}`);
124
+ }
125
+ }
126
+
127
+ if (results.created.length > 0) {
128
+ console.log(`\n${DIM}Run cursor-lint to check these rules${RESET}\n`);
129
+ }
130
+
131
+ process.exit(results.failed.length > 0 ? 1 : 0);
132
+
133
+ } else if (isFix) {
89
134
  console.log(`\nšŸ”§ cursor-lint v${VERSION} --fix\n`);
90
135
  console.log(`Scanning ${cwd} for fixable issues...\n`);
91
136
 
@@ -246,6 +291,8 @@ async function main() {
246
291
  if (totalErrors > 0) {
247
292
  console.log(`${DIM}Need help fixing these? Get a full setup review:${RESET}`);
248
293
  console.log(`${CYAN}https://cursorrulespacks.gumroad.com/l/cursor-setup-audit${RESET}\n`);
294
+ } else if (totalPassed > 0) {
295
+ console.log(`${DIM}If cursor-lint saved you time: ${CYAN}https://github.com/cursorrulespacks/cursor-lint${RESET} ${DIM}(⭐ helps others find it)${RESET}\n`);
249
296
  }
250
297
 
251
298
  process.exit(totalErrors > 0 ? 1 : 0);
@@ -0,0 +1,156 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const https = require('https');
4
+
5
+ const BASE_URL = 'https://raw.githubusercontent.com/cursorrulespacks/cursorrules-collection/main/rules-mdc/';
6
+
7
+ const PKG_DEP_MAP = {
8
+ 'react': 'frameworks/react.mdc',
9
+ 'next': 'frameworks/nextjs.mdc',
10
+ 'vue': 'frameworks/vue.mdc',
11
+ 'svelte': 'frameworks/svelte.mdc',
12
+ 'express': 'frameworks/express.mdc',
13
+ '@nestjs/core': 'frameworks/nestjs.mdc',
14
+ 'prisma': 'tools/prisma.mdc',
15
+ 'drizzle-orm': 'tools/drizzle.mdc',
16
+ };
17
+
18
+ const REQ_DEP_MAP = {
19
+ 'django': 'frameworks/django.mdc',
20
+ 'fastapi': 'frameworks/fastapi.mdc',
21
+ 'flask': 'frameworks/flask.mdc',
22
+ 'pydantic': 'tools/pydantic.mdc',
23
+ 'sqlalchemy': 'tools/sqlalchemy.mdc',
24
+ 'pytest': 'tools/pytest.mdc',
25
+ };
26
+
27
+ function fetchFile(url) {
28
+ return new Promise((resolve, reject) => {
29
+ const get = (u) => {
30
+ https.get(u, (res) => {
31
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
32
+ get(res.headers.location);
33
+ return;
34
+ }
35
+ if (res.statusCode !== 200) {
36
+ reject(new Error(`HTTP ${res.statusCode} for ${u}`));
37
+ res.resume();
38
+ return;
39
+ }
40
+ let data = '';
41
+ res.on('data', (c) => data += c);
42
+ res.on('end', () => resolve(data));
43
+ res.on('error', reject);
44
+ }).on('error', reject);
45
+ };
46
+ get(url);
47
+ });
48
+ }
49
+
50
+ function detectStack(cwd) {
51
+ const detected = [];
52
+ const rules = new Map(); // rulePath -> stackName
53
+
54
+ // package.json
55
+ const pkgPath = path.join(cwd, 'package.json');
56
+ if (fs.existsSync(pkgPath)) {
57
+ try {
58
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
59
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
60
+ for (const [dep, rule] of Object.entries(PKG_DEP_MAP)) {
61
+ if (allDeps[dep]) {
62
+ detected.push(dep);
63
+ rules.set(rule, dep);
64
+ }
65
+ }
66
+ } catch {}
67
+ }
68
+
69
+ // tsconfig.json
70
+ if (fs.existsSync(path.join(cwd, 'tsconfig.json'))) {
71
+ detected.push('TypeScript');
72
+ rules.set('languages/typescript.mdc', 'TypeScript');
73
+ }
74
+
75
+ // Python
76
+ const reqPath = path.join(cwd, 'requirements.txt');
77
+ const hasPy = fs.existsSync(reqPath) || fs.readdirSync(cwd).some(f => f.endsWith('.py'));
78
+ if (hasPy) {
79
+ detected.push('Python');
80
+ rules.set('languages/python.mdc', 'Python');
81
+ }
82
+ if (fs.existsSync(reqPath)) {
83
+ try {
84
+ const req = fs.readFileSync(reqPath, 'utf8').toLowerCase();
85
+ for (const [dep, rule] of Object.entries(REQ_DEP_MAP)) {
86
+ if (req.includes(dep)) {
87
+ detected.push(dep);
88
+ rules.set(rule, dep);
89
+ }
90
+ }
91
+ } catch {}
92
+ }
93
+
94
+ // Cargo.toml
95
+ if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) {
96
+ detected.push('Rust');
97
+ rules.set('languages/rust.mdc', 'Rust');
98
+ }
99
+
100
+ // go.mod
101
+ if (fs.existsSync(path.join(cwd, 'go.mod'))) {
102
+ detected.push('Go');
103
+ rules.set('languages/go.mdc', 'Go');
104
+ }
105
+
106
+ // Dockerfile
107
+ if (fs.existsSync(path.join(cwd, 'Dockerfile'))) {
108
+ detected.push('Docker');
109
+ rules.set('tools/docker.mdc', 'Docker');
110
+ }
111
+
112
+ // CI/CD
113
+ if (fs.existsSync(path.join(cwd, '.github', 'workflows'))) {
114
+ detected.push('CI/CD');
115
+ rules.set('tools/ci-cd.mdc', 'CI/CD');
116
+ }
117
+
118
+ return { detected, rules };
119
+ }
120
+
121
+ async function generateRules(cwd) {
122
+ const { detected, rules } = detectStack(cwd);
123
+ const rulesDir = path.join(cwd, '.cursor', 'rules');
124
+ const created = [];
125
+ const skipped = [];
126
+ const failed = [];
127
+
128
+ if (rules.size === 0) {
129
+ return { detected, created, skipped, failed };
130
+ }
131
+
132
+ fs.mkdirSync(rulesDir, { recursive: true });
133
+
134
+ for (const [rulePath, stackName] of rules) {
135
+ const filename = path.basename(rulePath);
136
+ const destPath = path.join(rulesDir, filename);
137
+
138
+ if (fs.existsSync(destPath)) {
139
+ skipped.push({ file: filename, stack: stackName });
140
+ continue;
141
+ }
142
+
143
+ try {
144
+ const url = BASE_URL + rulePath;
145
+ const content = await fetchFile(url);
146
+ fs.writeFileSync(destPath, content, 'utf8');
147
+ created.push({ file: filename, stack: stackName });
148
+ } catch (err) {
149
+ failed.push({ file: filename, stack: stackName, error: err.message });
150
+ }
151
+ }
152
+
153
+ return { detected, created, skipped, failed };
154
+ }
155
+
156
+ module.exports = { generateRules };