cursor-lint 0.8.0 → 0.9.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/package.json +1 -1
- package/src/cli.js +22 -7
- package/src/generate.js +313 -22
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ const { fixProject } = require('./fix');
|
|
|
8
8
|
const { generateRules } = require('./generate');
|
|
9
9
|
const { checkVersions, checkRuleVersionMismatches } = require('./versions');
|
|
10
10
|
|
|
11
|
-
const VERSION = '0.
|
|
11
|
+
const VERSION = '0.9.0';
|
|
12
12
|
|
|
13
13
|
const RED = '\x1b[31m';
|
|
14
14
|
const YELLOW = '\x1b[33m';
|
|
@@ -207,20 +207,35 @@ async function main() {
|
|
|
207
207
|
console.log(`${CYAN}Detected:${RESET} ${results.detected.join(', ')}\n`);
|
|
208
208
|
} else {
|
|
209
209
|
console.log(`${YELLOW}No recognized stack detected.${RESET}`);
|
|
210
|
-
console.log(`${DIM}Supports: package.json, tsconfig.json, requirements.txt,
|
|
210
|
+
console.log(`${DIM}Supports: package.json, tsconfig.json, requirements.txt, pyproject.toml,${RESET}`);
|
|
211
|
+
console.log(`${DIM}Cargo.toml, go.mod, Gemfile, composer.json, pom.xml, build.gradle,${RESET}`);
|
|
212
|
+
console.log(`${DIM}Dockerfile, pubspec.yaml, mix.exs, build.sbt, *.csproj, and more${RESET}\n`);
|
|
211
213
|
process.exit(0);
|
|
212
214
|
}
|
|
213
215
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
216
|
+
const stackCreated = results.created.filter(r => !r.stack.startsWith('best-practice:'));
|
|
217
|
+
const practiceCreated = results.created.filter(r => r.stack.startsWith('best-practice:'));
|
|
218
|
+
const stackSkipped = results.skipped.filter(r => !r.stack.startsWith('best-practice:'));
|
|
219
|
+
const practiceSkipped = results.skipped.filter(r => r.stack.startsWith('best-practice:'));
|
|
220
|
+
|
|
221
|
+
if (stackCreated.length > 0) {
|
|
222
|
+
console.log(`${GREEN}Downloaded (stack rules):${RESET}`);
|
|
223
|
+
for (const r of stackCreated) {
|
|
217
224
|
console.log(` ${GREEN}✓${RESET} .cursor/rules/${r.file} ${DIM}(${r.stack})${RESET}`);
|
|
218
225
|
}
|
|
219
226
|
}
|
|
220
227
|
|
|
221
|
-
if (
|
|
228
|
+
if (practiceCreated.length > 0) {
|
|
229
|
+
console.log(`\n${GREEN}Downloaded (best practices):${RESET}`);
|
|
230
|
+
for (const r of practiceCreated) {
|
|
231
|
+
const label = r.stack.replace('best-practice: ', '');
|
|
232
|
+
console.log(` ${GREEN}✓${RESET} .cursor/rules/${r.file} ${DIM}(${label})${RESET}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (stackSkipped.length + practiceSkipped.length > 0) {
|
|
222
237
|
console.log(`\n${YELLOW}Skipped (already exist):${RESET}`);
|
|
223
|
-
for (const r of
|
|
238
|
+
for (const r of [...stackSkipped, ...practiceSkipped]) {
|
|
224
239
|
console.log(` ${YELLOW}⚠${RESET} .cursor/rules/${r.file}`);
|
|
225
240
|
}
|
|
226
241
|
}
|
package/src/generate.js
CHANGED
|
@@ -4,24 +4,99 @@ const https = require('https');
|
|
|
4
4
|
|
|
5
5
|
const BASE_URL = 'https://raw.githubusercontent.com/cursorrulespacks/cursorrules-collection/main/rules-mdc/';
|
|
6
6
|
|
|
7
|
+
// package.json dependencies → rule files
|
|
7
8
|
const PKG_DEP_MAP = {
|
|
9
|
+
// Frameworks
|
|
8
10
|
'react': 'frameworks/react.mdc',
|
|
9
11
|
'next': 'frameworks/nextjs.mdc',
|
|
10
12
|
'vue': 'frameworks/vue.mdc',
|
|
13
|
+
'nuxt': 'frameworks/nuxt.mdc',
|
|
11
14
|
'svelte': 'frameworks/svelte.mdc',
|
|
15
|
+
'@sveltejs/kit': 'frameworks/sveltekit.mdc',
|
|
12
16
|
'express': 'frameworks/express.mdc',
|
|
13
17
|
'@nestjs/core': 'frameworks/nestjs.mdc',
|
|
18
|
+
'@angular/core': 'frameworks/angular.mdc',
|
|
19
|
+
'astro': 'frameworks/astro.mdc',
|
|
20
|
+
'gatsby': 'frameworks/gatsby.mdc',
|
|
21
|
+
'remix': 'frameworks/remix.mdc',
|
|
22
|
+
'solid-js': 'frameworks/solid-js.mdc',
|
|
23
|
+
'hono': 'frameworks/hono.mdc',
|
|
24
|
+
'htmx.org': 'frameworks/htmx.mdc',
|
|
25
|
+
'electron': 'frameworks/electron.mdc',
|
|
26
|
+
'@tauri-apps/api': 'frameworks/tauri.mdc',
|
|
27
|
+
'expo': 'frameworks/expo.mdc',
|
|
28
|
+
'swr': 'frameworks/swr.mdc',
|
|
29
|
+
'@tanstack/react-query': 'frameworks/tanstack-query.mdc',
|
|
30
|
+
'zod': 'frameworks/zod.mdc',
|
|
31
|
+
'zustand': 'frameworks/zustand.mdc',
|
|
32
|
+
'@t3-oss/env-nextjs': 'frameworks/t3-stack.mdc',
|
|
33
|
+
'tailwindcss': 'frameworks/tailwind-css.mdc',
|
|
34
|
+
|
|
35
|
+
// Tools
|
|
14
36
|
'prisma': 'tools/prisma.mdc',
|
|
15
37
|
'drizzle-orm': 'tools/drizzle.mdc',
|
|
38
|
+
'@trpc/server': 'tools/trpc.mdc',
|
|
39
|
+
'graphql': 'tools/graphql.mdc',
|
|
40
|
+
'@supabase/supabase-js': 'tools/supabase.mdc',
|
|
41
|
+
'firebase': 'tools/firebase.mdc',
|
|
42
|
+
'convex': 'tools/convex.mdc',
|
|
43
|
+
'@clerk/nextjs': 'tools/clerk.mdc',
|
|
44
|
+
'next-auth': 'tools/nextauth.mdc',
|
|
45
|
+
'stripe': 'tools/stripe.mdc',
|
|
46
|
+
'@langchain/core': 'tools/langchain.mdc',
|
|
47
|
+
'mongodb': 'tools/mongodb.mdc',
|
|
48
|
+
'redis': 'tools/redis.mdc',
|
|
49
|
+
'jest': 'tools/jest.mdc',
|
|
50
|
+
'vitest': 'tools/vitest.mdc',
|
|
51
|
+
'cypress': 'tools/cypress.mdc',
|
|
52
|
+
'@playwright/test': 'tools/playwright.mdc',
|
|
53
|
+
'@storybook/react': 'tools/storybook.mdc',
|
|
54
|
+
'turborepo': 'tools/turborepo.mdc',
|
|
55
|
+
'bun': 'tools/bun.mdc',
|
|
16
56
|
};
|
|
17
57
|
|
|
18
|
-
|
|
58
|
+
// Python requirements.txt / pyproject.toml → rule files
|
|
59
|
+
const PY_DEP_MAP = {
|
|
19
60
|
'django': 'frameworks/django.mdc',
|
|
20
61
|
'fastapi': 'frameworks/fastapi.mdc',
|
|
21
62
|
'flask': 'frameworks/flask.mdc',
|
|
22
63
|
'pydantic': 'tools/pydantic.mdc',
|
|
23
64
|
'sqlalchemy': 'tools/sqlalchemy.mdc',
|
|
24
65
|
'pytest': 'tools/pytest.mdc',
|
|
66
|
+
'langchain': 'tools/langchain.mdc',
|
|
67
|
+
'ruff': 'tools/ruff.mdc',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Gemfile → rule files
|
|
71
|
+
const RUBY_DEP_MAP = {
|
|
72
|
+
'rails': 'frameworks/rails.mdc',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// composer.json → rule files
|
|
76
|
+
const PHP_DEP_MAP = {
|
|
77
|
+
'laravel/framework': 'frameworks/laravel.mdc',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// build.gradle / pom.xml → rule files
|
|
81
|
+
const JVM_DEP_MAP = {
|
|
82
|
+
'spring-boot': 'frameworks/spring-boot.mdc',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Best practices auto-included when certain conditions are met
|
|
86
|
+
const PRACTICE_TRIGGERS = {
|
|
87
|
+
// Always suggest these for any project with >3 detected deps
|
|
88
|
+
'practices/clean-code.mdc': { minDeps: 3, label: 'clean-code' },
|
|
89
|
+
'practices/error-handling.mdc': { minDeps: 3, label: 'error-handling' },
|
|
90
|
+
'practices/git-workflow.mdc': { files: ['.git'], label: 'git-workflow' },
|
|
91
|
+
'practices/testing.mdc': { deps: ['jest', 'vitest', 'cypress', '@playwright/test', 'pytest', 'mocha', 'ava'], label: 'testing' },
|
|
92
|
+
'practices/security.mdc': { minDeps: 5, label: 'security' },
|
|
93
|
+
'practices/documentation.mdc': { files: ['README.md'], label: 'documentation' },
|
|
94
|
+
'practices/api-design.mdc': { deps: ['express', '@nestjs/core', 'fastapi', 'flask', 'django', 'hono', '@trpc/server', 'graphql'], label: 'api-design' },
|
|
95
|
+
'practices/performance.mdc': { deps: ['react', 'next', 'vue', 'nuxt', '@angular/core', 'svelte'], label: 'performance' },
|
|
96
|
+
'practices/database-migrations.mdc': { deps: ['prisma', 'drizzle-orm', 'sqlalchemy', 'django'], label: 'database-migrations' },
|
|
97
|
+
'practices/monorepo.mdc': { files: ['pnpm-workspace.yaml', 'lerna.json'], deps: ['turborepo'], label: 'monorepo' },
|
|
98
|
+
'practices/accessibility.mdc': { deps: ['react', 'next', 'vue', '@angular/core', 'svelte'], label: 'accessibility' },
|
|
99
|
+
'practices/logging.mdc': { deps: ['express', '@nestjs/core', 'fastapi', 'flask', 'django', 'hono'], label: 'logging' },
|
|
25
100
|
};
|
|
26
101
|
|
|
27
102
|
function fetchFile(url) {
|
|
@@ -47,74 +122,290 @@ function fetchFile(url) {
|
|
|
47
122
|
});
|
|
48
123
|
}
|
|
49
124
|
|
|
125
|
+
function readPyDeps(cwd) {
|
|
126
|
+
const deps = [];
|
|
127
|
+
|
|
128
|
+
// requirements.txt
|
|
129
|
+
const reqPath = path.join(cwd, 'requirements.txt');
|
|
130
|
+
if (fs.existsSync(reqPath)) {
|
|
131
|
+
try {
|
|
132
|
+
const content = fs.readFileSync(reqPath, 'utf8').toLowerCase();
|
|
133
|
+
for (const dep of Object.keys(PY_DEP_MAP)) {
|
|
134
|
+
if (content.includes(dep)) deps.push(dep);
|
|
135
|
+
}
|
|
136
|
+
} catch {}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// pyproject.toml (rough match)
|
|
140
|
+
const pyprojectPath = path.join(cwd, 'pyproject.toml');
|
|
141
|
+
if (fs.existsSync(pyprojectPath)) {
|
|
142
|
+
try {
|
|
143
|
+
const content = fs.readFileSync(pyprojectPath, 'utf8').toLowerCase();
|
|
144
|
+
for (const dep of Object.keys(PY_DEP_MAP)) {
|
|
145
|
+
if (content.includes(dep)) deps.push(dep);
|
|
146
|
+
}
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return [...new Set(deps)];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function readRubyDeps(cwd) {
|
|
154
|
+
const gemfilePath = path.join(cwd, 'Gemfile');
|
|
155
|
+
if (!fs.existsSync(gemfilePath)) return [];
|
|
156
|
+
try {
|
|
157
|
+
const content = fs.readFileSync(gemfilePath, 'utf8').toLowerCase();
|
|
158
|
+
return Object.keys(RUBY_DEP_MAP).filter(dep => content.includes(dep));
|
|
159
|
+
} catch { return []; }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function readPhpDeps(cwd) {
|
|
163
|
+
const composerPath = path.join(cwd, 'composer.json');
|
|
164
|
+
if (!fs.existsSync(composerPath)) return [];
|
|
165
|
+
try {
|
|
166
|
+
const pkg = JSON.parse(fs.readFileSync(composerPath, 'utf8'));
|
|
167
|
+
const allDeps = { ...pkg.require, ...pkg['require-dev'] };
|
|
168
|
+
return Object.keys(PHP_DEP_MAP).filter(dep => allDeps[dep]);
|
|
169
|
+
} catch { return []; }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function readJvmDeps(cwd) {
|
|
173
|
+
const deps = [];
|
|
174
|
+
for (const file of ['build.gradle', 'build.gradle.kts', 'pom.xml']) {
|
|
175
|
+
const p = path.join(cwd, file);
|
|
176
|
+
if (fs.existsSync(p)) {
|
|
177
|
+
try {
|
|
178
|
+
const content = fs.readFileSync(p, 'utf8').toLowerCase();
|
|
179
|
+
for (const dep of Object.keys(JVM_DEP_MAP)) {
|
|
180
|
+
if (content.includes(dep)) deps.push(dep);
|
|
181
|
+
}
|
|
182
|
+
} catch {}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return [...new Set(deps)];
|
|
186
|
+
}
|
|
187
|
+
|
|
50
188
|
function detectStack(cwd) {
|
|
51
189
|
const detected = [];
|
|
52
190
|
const rules = new Map(); // rulePath -> stackName
|
|
191
|
+
const allDetectedDeps = [];
|
|
53
192
|
|
|
54
193
|
// package.json
|
|
55
194
|
const pkgPath = path.join(cwd, 'package.json');
|
|
195
|
+
let pkgDeps = {};
|
|
56
196
|
if (fs.existsSync(pkgPath)) {
|
|
57
197
|
try {
|
|
58
198
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
59
|
-
|
|
199
|
+
pkgDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
60
200
|
for (const [dep, rule] of Object.entries(PKG_DEP_MAP)) {
|
|
61
|
-
if (
|
|
201
|
+
if (pkgDeps[dep]) {
|
|
62
202
|
detected.push(dep);
|
|
203
|
+
allDetectedDeps.push(dep);
|
|
63
204
|
rules.set(rule, dep);
|
|
64
205
|
}
|
|
65
206
|
}
|
|
66
207
|
} catch {}
|
|
67
208
|
}
|
|
68
209
|
|
|
69
|
-
// tsconfig.json
|
|
210
|
+
// tsconfig.json → TypeScript
|
|
70
211
|
if (fs.existsSync(path.join(cwd, 'tsconfig.json'))) {
|
|
71
212
|
detected.push('TypeScript');
|
|
72
213
|
rules.set('languages/typescript.mdc', 'TypeScript');
|
|
73
214
|
}
|
|
74
215
|
|
|
216
|
+
// JavaScript (package.json exists but no TS)
|
|
217
|
+
if (fs.existsSync(pkgPath) && !fs.existsSync(path.join(cwd, 'tsconfig.json'))) {
|
|
218
|
+
detected.push('JavaScript');
|
|
219
|
+
rules.set('languages/javascript.mdc', 'JavaScript');
|
|
220
|
+
}
|
|
221
|
+
|
|
75
222
|
// Python
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
223
|
+
const hasPyFile = (() => { try { return fs.readdirSync(cwd).some(f => f.endsWith('.py')); } catch { return false; } })();
|
|
224
|
+
const hasPyProject = fs.existsSync(path.join(cwd, 'requirements.txt')) ||
|
|
225
|
+
fs.existsSync(path.join(cwd, 'pyproject.toml')) ||
|
|
226
|
+
fs.existsSync(path.join(cwd, 'setup.py')) ||
|
|
227
|
+
hasPyFile;
|
|
228
|
+
if (hasPyProject) {
|
|
79
229
|
detected.push('Python');
|
|
80
230
|
rules.set('languages/python.mdc', 'Python');
|
|
231
|
+
const pyDeps = readPyDeps(cwd);
|
|
232
|
+
for (const dep of pyDeps) {
|
|
233
|
+
detected.push(dep);
|
|
234
|
+
allDetectedDeps.push(dep);
|
|
235
|
+
rules.set(PY_DEP_MAP[dep], dep);
|
|
236
|
+
}
|
|
81
237
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
238
|
+
|
|
239
|
+
// Ruby
|
|
240
|
+
if (fs.existsSync(path.join(cwd, 'Gemfile'))) {
|
|
241
|
+
detected.push('Ruby');
|
|
242
|
+
rules.set('languages/ruby.mdc', 'Ruby');
|
|
243
|
+
for (const dep of readRubyDeps(cwd)) {
|
|
244
|
+
detected.push(dep);
|
|
245
|
+
allDetectedDeps.push(dep);
|
|
246
|
+
rules.set(RUBY_DEP_MAP[dep], dep);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// PHP
|
|
251
|
+
if (fs.existsSync(path.join(cwd, 'composer.json'))) {
|
|
252
|
+
detected.push('PHP');
|
|
253
|
+
rules.set('languages/php.mdc', 'PHP');
|
|
254
|
+
for (const dep of readPhpDeps(cwd)) {
|
|
255
|
+
detected.push(dep);
|
|
256
|
+
allDetectedDeps.push(dep);
|
|
257
|
+
rules.set(PHP_DEP_MAP[dep], dep);
|
|
258
|
+
}
|
|
92
259
|
}
|
|
93
260
|
|
|
94
|
-
//
|
|
261
|
+
// Rust
|
|
95
262
|
if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) {
|
|
96
263
|
detected.push('Rust');
|
|
97
264
|
rules.set('languages/rust.mdc', 'Rust');
|
|
98
265
|
}
|
|
99
266
|
|
|
100
|
-
//
|
|
267
|
+
// Go
|
|
101
268
|
if (fs.existsSync(path.join(cwd, 'go.mod'))) {
|
|
102
269
|
detected.push('Go');
|
|
103
270
|
rules.set('languages/go.mdc', 'Go');
|
|
104
271
|
}
|
|
105
272
|
|
|
106
|
-
//
|
|
107
|
-
if (fs.existsSync(path.join(cwd, '
|
|
273
|
+
// Java
|
|
274
|
+
if (fs.existsSync(path.join(cwd, 'pom.xml')) || fs.existsSync(path.join(cwd, 'build.gradle')) || fs.existsSync(path.join(cwd, 'build.gradle.kts'))) {
|
|
275
|
+
detected.push('Java');
|
|
276
|
+
rules.set('languages/java.mdc', 'Java');
|
|
277
|
+
for (const dep of readJvmDeps(cwd)) {
|
|
278
|
+
detected.push(dep);
|
|
279
|
+
allDetectedDeps.push(dep);
|
|
280
|
+
rules.set(JVM_DEP_MAP[dep], dep);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Kotlin
|
|
285
|
+
if (fs.existsSync(path.join(cwd, 'build.gradle.kts'))) {
|
|
286
|
+
detected.push('Kotlin');
|
|
287
|
+
rules.set('languages/kotlin.mdc', 'Kotlin');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Swift
|
|
291
|
+
if (fs.existsSync(path.join(cwd, 'Package.swift'))) {
|
|
292
|
+
detected.push('Swift');
|
|
293
|
+
rules.set('languages/swift.mdc', 'Swift');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Elixir
|
|
297
|
+
if (fs.existsSync(path.join(cwd, 'mix.exs'))) {
|
|
298
|
+
detected.push('Elixir');
|
|
299
|
+
rules.set('languages/elixir.mdc', 'Elixir');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Scala
|
|
303
|
+
if (fs.existsSync(path.join(cwd, 'build.sbt'))) {
|
|
304
|
+
detected.push('Scala');
|
|
305
|
+
rules.set('languages/scala.mdc', 'Scala');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// C#
|
|
309
|
+
const hasCsproj = (() => { try { return fs.readdirSync(cwd).some(f => f.endsWith('.csproj') || f.endsWith('.sln')); } catch { return false; } })();
|
|
310
|
+
if (hasCsproj) {
|
|
311
|
+
detected.push('C#');
|
|
312
|
+
rules.set('languages/csharp.mdc', 'C#');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// C++
|
|
316
|
+
if (fs.existsSync(path.join(cwd, 'CMakeLists.txt')) || fs.existsSync(path.join(cwd, 'Makefile'))) {
|
|
317
|
+
const hasCpp = (() => { try { return fs.readdirSync(cwd).some(f => /\.(cpp|cc|cxx|hpp|h)$/.test(f)); } catch { return false; } })();
|
|
318
|
+
if (hasCpp) {
|
|
319
|
+
detected.push('C++');
|
|
320
|
+
rules.set('languages/cpp.mdc', 'C++');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Flutter (pubspec.yaml)
|
|
325
|
+
if (fs.existsSync(path.join(cwd, 'pubspec.yaml'))) {
|
|
326
|
+
detected.push('Flutter');
|
|
327
|
+
rules.set('frameworks/flutter.mdc', 'Flutter');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Docker
|
|
331
|
+
if (fs.existsSync(path.join(cwd, 'Dockerfile')) || fs.existsSync(path.join(cwd, 'docker-compose.yml')) || fs.existsSync(path.join(cwd, 'docker-compose.yaml'))) {
|
|
108
332
|
detected.push('Docker');
|
|
109
333
|
rules.set('tools/docker.mdc', 'Docker');
|
|
110
334
|
}
|
|
111
335
|
|
|
336
|
+
// Kubernetes
|
|
337
|
+
const k8sDir = path.join(cwd, 'k8s');
|
|
338
|
+
if (fs.existsSync(k8sDir) || fs.existsSync(path.join(cwd, 'kubernetes'))) {
|
|
339
|
+
detected.push('Kubernetes');
|
|
340
|
+
rules.set('tools/kubernetes.mdc', 'Kubernetes');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Terraform
|
|
344
|
+
const hasTf = (() => { try { return fs.readdirSync(cwd).some(f => f.endsWith('.tf')); } catch { return false; } })();
|
|
345
|
+
if (hasTf) {
|
|
346
|
+
detected.push('Terraform');
|
|
347
|
+
rules.set('tools/terraform.mdc', 'Terraform');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Deno
|
|
351
|
+
if (fs.existsSync(path.join(cwd, 'deno.json')) || fs.existsSync(path.join(cwd, 'deno.jsonc'))) {
|
|
352
|
+
detected.push('Deno');
|
|
353
|
+
rules.set('tools/deno.mdc', 'Deno');
|
|
354
|
+
}
|
|
355
|
+
|
|
112
356
|
// CI/CD
|
|
113
|
-
if (fs.existsSync(path.join(cwd, '.github', 'workflows'))) {
|
|
357
|
+
if (fs.existsSync(path.join(cwd, '.github', 'workflows')) || fs.existsSync(path.join(cwd, '.gitlab-ci.yml'))) {
|
|
114
358
|
detected.push('CI/CD');
|
|
115
359
|
rules.set('tools/ci-cd.mdc', 'CI/CD');
|
|
116
360
|
}
|
|
117
361
|
|
|
362
|
+
// Nginx
|
|
363
|
+
const hasNginx = (() => { try { return fs.readdirSync(cwd).some(f => f.includes('nginx')); } catch { return false; } })();
|
|
364
|
+
if (hasNginx) {
|
|
365
|
+
detected.push('Nginx');
|
|
366
|
+
rules.set('tools/nginx.mdc', 'Nginx');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// SQLite
|
|
370
|
+
if (pkgDeps['better-sqlite3'] || pkgDeps['sqlite3']) {
|
|
371
|
+
detected.push('SQLite');
|
|
372
|
+
allDetectedDeps.push('sqlite3');
|
|
373
|
+
rules.set('tools/sqlite.mdc', 'SQLite');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// PostgreSQL
|
|
377
|
+
if (pkgDeps['pg'] || pkgDeps['postgres']) {
|
|
378
|
+
detected.push('PostgreSQL');
|
|
379
|
+
allDetectedDeps.push('pg');
|
|
380
|
+
rules.set('tools/postgresql.mdc', 'PostgreSQL');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// AWS
|
|
384
|
+
if (pkgDeps['@aws-sdk/client-s3'] || pkgDeps['aws-sdk'] || fs.existsSync(path.join(cwd, 'serverless.yml')) || fs.existsSync(path.join(cwd, 'template.yaml'))) {
|
|
385
|
+
detected.push('AWS');
|
|
386
|
+
allDetectedDeps.push('aws-sdk');
|
|
387
|
+
rules.set('tools/aws.mdc', 'AWS');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Best practices — auto-suggest based on project signals
|
|
391
|
+
for (const [rulePath, trigger] of Object.entries(PRACTICE_TRIGGERS)) {
|
|
392
|
+
let shouldInclude = false;
|
|
393
|
+
|
|
394
|
+
if (trigger.minDeps && allDetectedDeps.length >= trigger.minDeps) {
|
|
395
|
+
shouldInclude = true;
|
|
396
|
+
}
|
|
397
|
+
if (trigger.deps && trigger.deps.some(d => allDetectedDeps.includes(d) || pkgDeps[d])) {
|
|
398
|
+
shouldInclude = true;
|
|
399
|
+
}
|
|
400
|
+
if (trigger.files && trigger.files.some(f => fs.existsSync(path.join(cwd, f)))) {
|
|
401
|
+
shouldInclude = true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (shouldInclude) {
|
|
405
|
+
rules.set(rulePath, `best-practice: ${trigger.label}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
118
409
|
return { detected, rules };
|
|
119
410
|
}
|
|
120
411
|
|